Compare commits

..
48 Commits
Author SHA1 Message Date
semantic-release-bot 0d06875bb7 chore(release): 1.18.0 [skip ci]
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-22)

### Bug Fixes

* add .releaserc.json for semantic-release configuration ([047d718](https://github.com/asepharyana/zesdex/commit/047d7183d72adaa0dd1a18a7f92b22d67d5aa85f))
* **clippy:** replace type annotation with type alias + .insert() to avoid trivial_cast ([ffefa8c](https://github.com/asepharyana/zesdex/commit/ffefa8c07facbba83778f426ca4715569556b08a))
* **rust:** remove unused imports, variables, and dead code causing CI build failures ([eb8cc17](https://github.com/asepharyana/zesdex/commit/eb8cc1799359a5ef5b4a4ee3a4202b87e80bfe6c))
* **rust:** resolve all clippy warnings treated as errors in CI ([6b90c7e](https://github.com/asepharyana/zesdex/commit/6b90c7eb0d17d9281a377454e76dcc88a4232bc7))
* **tui:** resolve remaining clippy errors in workspace ([eed4025](https://github.com/asepharyana/zesdex/commit/eed4025918de65eb2f3fd98cd3350d62f3322132))

### Features

* add semantic search tool for code symbol indexing and searching ([fef3c92](https://github.com/asepharyana/zesdex/commit/fef3c925cd02e1d76e2d990cc6fa4e4cba64fab0))
* Add SOLID principles and TDD reference documentation ([55677dd](https://github.com/asepharyana/zesdex/commit/55677dd67100813a5ed2b0bbf02257b57875ea07))
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **agent:** add AI summarization for conversation history compacting ([873f870](https://github.com/asepharyana/zesdex/commit/873f870e233dd0d2c6f0cce935f83fb8ce373505))
* **agent:** implement agent execution engine and turn handling with background processing ([efcd191](https://github.com/asepharyana/zesdex/commit/efcd191f9698023944f9096a86c1efa47e412b6b))
* **best_practice:** add code quality scanning and commit message validation ([8ecc588](https://github.com/asepharyana/zesdex/commit/8ecc588a3e125dc6a4b2e7eb4c12c6b344449c07))
* centralize default constants and refactor overlay enter handling in TUI ([d615090](https://github.com/asepharyana/zesdex/commit/d615090dcd34bdb93be22c1c7ce4ba674363c258))
* enhance context gathering in auto-review engine and agent runner ([dcc8c3e](https://github.com/asepharyana/zesdex/commit/dcc8c3ee42ef568843e04143048d799bc0882288))
* enhance explore phase with TUI workflow event handling ([b3a4d13](https://github.com/asepharyana/zesdex/commit/b3a4d131d13d94bd307720cb50c2d02dedf92873))
* implement Component trait for modular UI components and refactor TUI views to use it ([d59713d](https://github.com/asepharyana/zesdex/commit/d59713d3e3aa4325f9753bbddd068654e442f6ef))
* implement mandatory explore phase with parallel subagents ([f368f3a](https://github.com/asepharyana/zesdex/commit/f368f3a1c0d2df02eb50542a81bcff96d9cdc420))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* refactor auto-review engine to use spawn_subagent for improved thread handling ([094eb4b](https://github.com/asepharyana/zesdex/commit/094eb4b8baa5abd878dcf5b611cf615955bc79eb))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** introduce comprehensive state management for TUI interface ([8c58faf](https://github.com/asepharyana/zesdex/commit/8c58faf2920b7341dd50ace15044ff113cefb576))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
2026-07-22 07:33:49 +00:00
Cyrene (Mem) ffefa8c07f fix(clippy): replace type annotation with type alias + .insert() to avoid trivial_cast
- Use type alias ExtDispatch for the HashMap type (fixes type_complexity)
- Use .insert() instead of HashMap::from([...]) to avoid fn ptr trivial casts
- Remove all as fn(...) casts to avoid clippy::trivial_casts
2026-07-22 14:29:50 +07:00
semantic-release-bot 883908ab55 chore(release): 1.18.0 [skip ci]
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-22)

### Bug Fixes

* add .releaserc.json for semantic-release configuration ([047d718](https://github.com/asepharyana/zesdex/commit/047d7183d72adaa0dd1a18a7f92b22d67d5aa85f))
* **rust:** remove unused imports, variables, and dead code causing CI build failures ([eb8cc17](https://github.com/asepharyana/zesdex/commit/eb8cc1799359a5ef5b4a4ee3a4202b87e80bfe6c))
* **rust:** resolve all clippy warnings treated as errors in CI ([6b90c7e](https://github.com/asepharyana/zesdex/commit/6b90c7eb0d17d9281a377454e76dcc88a4232bc7))
* **tui:** resolve remaining clippy errors in workspace ([eed4025](https://github.com/asepharyana/zesdex/commit/eed4025918de65eb2f3fd98cd3350d62f3322132))

### Features

* add semantic search tool for code symbol indexing and searching ([fef3c92](https://github.com/asepharyana/zesdex/commit/fef3c925cd02e1d76e2d990cc6fa4e4cba64fab0))
* Add SOLID principles and TDD reference documentation ([55677dd](https://github.com/asepharyana/zesdex/commit/55677dd67100813a5ed2b0bbf02257b57875ea07))
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **agent:** add AI summarization for conversation history compacting ([873f870](https://github.com/asepharyana/zesdex/commit/873f870e233dd0d2c6f0cce935f83fb8ce373505))
* **agent:** implement agent execution engine and turn handling with background processing ([efcd191](https://github.com/asepharyana/zesdex/commit/efcd191f9698023944f9096a86c1efa47e412b6b))
* **best_practice:** add code quality scanning and commit message validation ([8ecc588](https://github.com/asepharyana/zesdex/commit/8ecc588a3e125dc6a4b2e7eb4c12c6b344449c07))
* centralize default constants and refactor overlay enter handling in TUI ([d615090](https://github.com/asepharyana/zesdex/commit/d615090dcd34bdb93be22c1c7ce4ba674363c258))
* enhance context gathering in auto-review engine and agent runner ([dcc8c3e](https://github.com/asepharyana/zesdex/commit/dcc8c3ee42ef568843e04143048d799bc0882288))
* enhance explore phase with TUI workflow event handling ([b3a4d13](https://github.com/asepharyana/zesdex/commit/b3a4d131d13d94bd307720cb50c2d02dedf92873))
* implement Component trait for modular UI components and refactor TUI views to use it ([d59713d](https://github.com/asepharyana/zesdex/commit/d59713d3e3aa4325f9753bbddd068654e442f6ef))
* implement mandatory explore phase with parallel subagents ([f368f3a](https://github.com/asepharyana/zesdex/commit/f368f3a1c0d2df02eb50542a81bcff96d9cdc420))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* refactor auto-review engine to use spawn_subagent for improved thread handling ([094eb4b](https://github.com/asepharyana/zesdex/commit/094eb4b8baa5abd878dcf5b611cf615955bc79eb))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** introduce comprehensive state management for TUI interface ([8c58faf](https://github.com/asepharyana/zesdex/commit/8c58faf2920b7341dd50ace15044ff113cefb576))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
2026-07-22 07:26:13 +00:00
Cyrene (Mem) eed4025918 fix(tui): resolve remaining clippy errors in workspace
- turn.rs: remove needless ref borrowing
- view/mod.rs: use unit struct directly instead of ::default() for
  ChatComponent, InputComponent, StatusBarComponent, SidebarComponent

These were uncovered after fixing the infrastructure crate errors.
2026-07-22 14:22:11 +07:00
Cyrene (Mem) 6b90c7eb0d fix(rust): resolve all clippy warnings treated as errors in CI
Fix 18 clippy errors across 7 files:
- lib.rs: change doc comment to regular comment (empty line after doc)
- arch_audit.rs: replace format!() with string literal, use is_none_or
- code_quality.rs: collapsible if, map_or → is_none_or
- commit.rs: collapsible match guard
- explore.rs: needless_range_loop → iterator enumerate
- skills.rs: map_or(false,...) → is_some_and
- semantic_search.rs: map_or → is_none_or, sort_by → sort_by_key,
  remove explicit type to avoid type_complexity

CI was failing with 'error: could not compile zesdex-infrastructure
due to 18 previous errors' at clippy step.
2026-07-22 14:16:56 +07:00
semantic-release-bot 958645ed7f chore(release): 1.18.0 [skip ci]
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-22)

### Bug Fixes

* add .releaserc.json for semantic-release configuration ([047d718](https://github.com/asepharyana/zesdex/commit/047d7183d72adaa0dd1a18a7f92b22d67d5aa85f))
* **rust:** remove unused imports, variables, and dead code causing CI build failures ([eb8cc17](https://github.com/asepharyana/zesdex/commit/eb8cc1799359a5ef5b4a4ee3a4202b87e80bfe6c))

### Features

* add semantic search tool for code symbol indexing and searching ([fef3c92](https://github.com/asepharyana/zesdex/commit/fef3c925cd02e1d76e2d990cc6fa4e4cba64fab0))
* Add SOLID principles and TDD reference documentation ([55677dd](https://github.com/asepharyana/zesdex/commit/55677dd67100813a5ed2b0bbf02257b57875ea07))
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **agent:** add AI summarization for conversation history compacting ([873f870](https://github.com/asepharyana/zesdex/commit/873f870e233dd0d2c6f0cce935f83fb8ce373505))
* **agent:** implement agent execution engine and turn handling with background processing ([efcd191](https://github.com/asepharyana/zesdex/commit/efcd191f9698023944f9096a86c1efa47e412b6b))
* **best_practice:** add code quality scanning and commit message validation ([8ecc588](https://github.com/asepharyana/zesdex/commit/8ecc588a3e125dc6a4b2e7eb4c12c6b344449c07))
* centralize default constants and refactor overlay enter handling in TUI ([d615090](https://github.com/asepharyana/zesdex/commit/d615090dcd34bdb93be22c1c7ce4ba674363c258))
* enhance context gathering in auto-review engine and agent runner ([dcc8c3e](https://github.com/asepharyana/zesdex/commit/dcc8c3ee42ef568843e04143048d799bc0882288))
* enhance explore phase with TUI workflow event handling ([b3a4d13](https://github.com/asepharyana/zesdex/commit/b3a4d131d13d94bd307720cb50c2d02dedf92873))
* implement Component trait for modular UI components and refactor TUI views to use it ([d59713d](https://github.com/asepharyana/zesdex/commit/d59713d3e3aa4325f9753bbddd068654e442f6ef))
* implement mandatory explore phase with parallel subagents ([f368f3a](https://github.com/asepharyana/zesdex/commit/f368f3a1c0d2df02eb50542a81bcff96d9cdc420))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* refactor auto-review engine to use spawn_subagent for improved thread handling ([094eb4b](https://github.com/asepharyana/zesdex/commit/094eb4b8baa5abd878dcf5b611cf615955bc79eb))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** introduce comprehensive state management for TUI interface ([8c58faf](https://github.com/asepharyana/zesdex/commit/8c58faf2920b7341dd50ace15044ff113cefb576))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
2026-07-22 07:01:54 +00:00
Cyrene (Mem) eb8cc17993 fix(rust): remove unused imports, variables, and dead code causing CI build failures
- Remove unused imports: AtomicBool, Future (provider.rs), debug (executor.rs)
- Remove unused import WorkflowScript (workflow.rs)
- Remove unused variables: tool_name, t_ctx, t_args (executor.rs)
- Remove dead variable class_indent (semantic_search.rs)

CI was failing with 'error: could not compile zesdex-infrastructure
due to 10 previous errors' caused by -D warnings treating unused
code as compilation errors.
2026-07-22 13:57:50 +07:00
asepharyana b3a4d131d1 feat: enhance explore phase with TUI workflow event handling 2026-07-21 07:47:35 +07:00
asepharyana f368f3a1c0 feat: implement mandatory explore phase with parallel subagents
- Added `ExploreService` trait and `ExploreServiceImpl` struct to handle the exploration of codebase context before agent turns.
- Implemented three parallel subagents: Code Structure, Symbol Index, and Semantic Context, each with specific directives.
- Integrated the explore phase into the agent turn process, ensuring that each turn starts with a consolidated context message.
- Enhanced `spawn_agent_turn` function to include explore service wiring and context preparation.
2026-07-21 07:41:28 +07:00
asepharyana 8ecc588a3e feat(best_practice): add code quality scanning and commit message validation
- Implemented a code quality scanner that checks for common clean-code violations in Rust source files, including missing documentation, usage of `.unwrap()` in production code, and commented-out code.
- Introduced a commit message validator that follows the Conventional Commits specification, ensuring proper formatting and providing suggestions for invalid messages.
- Created a unified BestPracticeEngine to encapsulate the functionalities of skills, architecture audits, code quality checks, and commit message validation.
- Added tests for both the code quality scanner and commit message validator to ensure reliability and correctness.
2026-07-21 07:21:22 +07:00
asepharyana 55677dd671 feat: Add SOLID principles and TDD reference documentation
- Created solid.md to document the SOLID principles for clean code practices.
- Created tdd.md to outline Test Driven Development principles and practices.
- Added kana-rust-backend-best-practice.md as a reference guide for building a Rust backend using Axum and SeaORM.
- Established push-flow-convention.md to enforce pre-commit and pre-push hooks with versioning rules.
- Introduced AGENTS.md to provide guidance on best practices and available commands for Kilo.
- Configured kilo.json to include new skills and agents for enhanced functionality.
- Added lefthook.yml for managing git hooks to ensure code quality and adherence to conventions.
2026-07-21 07:10:17 +07:00
asepharyana d615090dcd feat: centralize default constants and refactor overlay enter handling in TUI 2026-07-21 07:00:15 +07:00
asepharyana 8c58faf292 feat(tui): introduce comprehensive state management for TUI interface
- Add AppStateRest as the central state struct for managing TUI state.
- Implement InputState for handling user input, autocomplete, and history.
- Create MiscState to manage overlays, notifications, and editor state.
- Introduce ScrollState for viewport scrolling functionality.
- Develop TranscriptCache for efficient message rendering in the chat pane.
- Implement SimpleAgent and SimpleWorkflowEngine for agent lifecycle management.
- Add helper functions for managing effort levels and token counting.
- Organize state-related modules for better maintainability and clarity.
2026-07-21 06:42:53 +07:00
asepharyana 802346f909 Refactor subagent and workflow domain models; migrate access tiers and events to domain module
- Moved `AccessTier` and `SubagentEvent` enums to `zesdex_domain::subagent`.
- Consolidated workflow-related types into `zesdex_domain::workflow`.
- Updated references across the codebase to use the new domain models.
- Refactored tool execution logic to utilize a new `ToolExecutor` trait.
- Enhanced `AgentTurnService` to handle tool calls and events more effectively.
- Adjusted API handlers and state management to align with new domain structure.
2026-07-21 06:42:53 +07:00
semantic-release-bot 552bc5bc63 chore(release): 1.18.0 [skip ci]
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)

### Bug Fixes

* add .releaserc.json for semantic-release configuration ([047d718](https://github.com/asepharyana/zesdex/commit/047d7183d72adaa0dd1a18a7f92b22d67d5aa85f))

### Features

* add semantic search tool for code symbol indexing and searching ([fef3c92](https://github.com/asepharyana/zesdex/commit/fef3c925cd02e1d76e2d990cc6fa4e4cba64fab0))
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **agent:** add AI summarization for conversation history compacting ([873f870](https://github.com/asepharyana/zesdex/commit/873f870e233dd0d2c6f0cce935f83fb8ce373505))
* **agent:** implement agent execution engine and turn handling with background processing ([efcd191](https://github.com/asepharyana/zesdex/commit/efcd191f9698023944f9096a86c1efa47e412b6b))
* enhance context gathering in auto-review engine and agent runner ([dcc8c3e](https://github.com/asepharyana/zesdex/commit/dcc8c3ee42ef568843e04143048d799bc0882288))
* implement Component trait for modular UI components and refactor TUI views to use it ([d59713d](https://github.com/asepharyana/zesdex/commit/d59713d3e3aa4325f9753bbddd068654e442f6ef))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* refactor auto-review engine to use spawn_subagent for improved thread handling ([094eb4b](https://github.com/asepharyana/zesdex/commit/094eb4b8baa5abd878dcf5b611cf615955bc79eb))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
2026-07-20 23:11:52 +00:00
asepharyana d59713d3e3 feat: implement Component trait for modular UI components and refactor TUI views to use it 2026-07-21 06:08:00 +07:00
semantic-release-bot 6ab532018a chore(release): 1.18.0 [skip ci]
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)

### Bug Fixes

* add .releaserc.json for semantic-release configuration ([047d718](https://github.com/asepharyana/zesdex/commit/047d7183d72adaa0dd1a18a7f92b22d67d5aa85f))

### Features

* add semantic search tool for code symbol indexing and searching ([fef3c92](https://github.com/asepharyana/zesdex/commit/fef3c925cd02e1d76e2d990cc6fa4e4cba64fab0))
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **agent:** add AI summarization for conversation history compacting ([873f870](https://github.com/asepharyana/zesdex/commit/873f870e233dd0d2c6f0cce935f83fb8ce373505))
* **agent:** implement agent execution engine and turn handling with background processing ([efcd191](https://github.com/asepharyana/zesdex/commit/efcd191f9698023944f9096a86c1efa47e412b6b))
* enhance context gathering in auto-review engine and agent runner ([dcc8c3e](https://github.com/asepharyana/zesdex/commit/dcc8c3ee42ef568843e04143048d799bc0882288))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* refactor auto-review engine to use spawn_subagent for improved thread handling ([094eb4b](https://github.com/asepharyana/zesdex/commit/094eb4b8baa5abd878dcf5b611cf615955bc79eb))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
2026-07-20 10:48:37 +00:00
asepharyana 047d7183d7 fix: add .releaserc.json for semantic-release configuration
- Configure branches, plugins, and release workflow for Rust project
- Fixes Release workflow failing with 'tag already exists'
2026-07-20 17:44:33 +07:00
semantic-release-bot 4d85e5144b chore(release): 1.18.0 [skip ci]
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)

### Features

* add semantic search tool for code symbol indexing and searching ([fef3c92](https://github.com/asepharyana/zesdex/commit/fef3c925cd02e1d76e2d990cc6fa4e4cba64fab0))
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **agent:** add AI summarization for conversation history compacting ([873f870](https://github.com/asepharyana/zesdex/commit/873f870e233dd0d2c6f0cce935f83fb8ce373505))
* **agent:** implement agent execution engine and turn handling with background processing ([efcd191](https://github.com/asepharyana/zesdex/commit/efcd191f9698023944f9096a86c1efa47e412b6b))
* enhance context gathering in auto-review engine and agent runner ([dcc8c3e](https://github.com/asepharyana/zesdex/commit/dcc8c3ee42ef568843e04143048d799bc0882288))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* refactor auto-review engine to use spawn_subagent for improved thread handling ([094eb4b](https://github.com/asepharyana/zesdex/commit/094eb4b8baa5abd878dcf5b611cf615955bc79eb))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
2026-07-20 10:36:16 +00:00
asepharyana dcc8c3ee42 feat: enhance context gathering in auto-review engine and agent runner 2026-07-20 17:32:31 +07:00
semantic-release-bot 289c58ef36 chore(release): 1.18.0 [skip ci]
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)

### Features

* add semantic search tool for code symbol indexing and searching ([fef3c92](https://github.com/asepharyana/zesdex/commit/fef3c925cd02e1d76e2d990cc6fa4e4cba64fab0))
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **agent:** add AI summarization for conversation history compacting ([873f870](https://github.com/asepharyana/zesdex/commit/873f870e233dd0d2c6f0cce935f83fb8ce373505))
* **agent:** implement agent execution engine and turn handling with background processing ([efcd191](https://github.com/asepharyana/zesdex/commit/efcd191f9698023944f9096a86c1efa47e412b6b))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* refactor auto-review engine to use spawn_subagent for improved thread handling ([094eb4b](https://github.com/asepharyana/zesdex/commit/094eb4b8baa5abd878dcf5b611cf615955bc79eb))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
2026-07-20 10:29:23 +00:00
asepharyana 094eb4b8ba feat: refactor auto-review engine to use spawn_subagent for improved thread handling 2026-07-20 17:25:23 +07:00
asepharyana dd7825b481 refactor: streamline agent turn handling and background review process 2026-07-20 17:25:23 +07:00
semantic-release-bot 4c186b62d4 chore(release): 1.18.0 [skip ci]
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)

### Features

* add semantic search tool for code symbol indexing and searching ([fef3c92](https://github.com/asepharyana/zesdex/commit/fef3c925cd02e1d76e2d990cc6fa4e4cba64fab0))
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **agent:** add AI summarization for conversation history compacting ([873f870](https://github.com/asepharyana/zesdex/commit/873f870e233dd0d2c6f0cce935f83fb8ce373505))
* **agent:** implement agent execution engine and turn handling with background processing ([efcd191](https://github.com/asepharyana/zesdex/commit/efcd191f9698023944f9096a86c1efa47e412b6b))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
2026-07-20 10:03:25 +00:00
asepharyana fef3c925cd feat: add semantic search tool for code symbol indexing and searching
- Implemented a new tool for semantic code search that indexes Rust code symbols (functions, structs, enums, traits, modules) and allows searching by name, concept, or meaning.
- Introduced a symbol index structure with methods for rebuilding the index and searching symbols.
- Added regex patterns for extracting various code symbols from Rust source files.
- Implemented scoring logic for search results based on exact matches, prefix matches, and context relevance.
- Created a web search tool that interacts with a SearXNG instance to fetch documentation and API information based on user queries.
- Added a diff preview overlay for rendering git diff output with color-coded additions and deletions in a TUI interface.
2026-07-20 16:59:27 +07:00
semantic-release-bot 785ae19757 chore(release): 1.18.0 [skip ci]
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)

### Features

* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **agent:** add AI summarization for conversation history compacting ([873f870](https://github.com/asepharyana/zesdex/commit/873f870e233dd0d2c6f0cce935f83fb8ce373505))
* **agent:** implement agent execution engine and turn handling with background processing ([efcd191](https://github.com/asepharyana/zesdex/commit/efcd191f9698023944f9096a86c1efa47e412b6b))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
2026-07-20 09:42:13 +00:00
asepharyana 873f870e23 feat(agent): add AI summarization for conversation history compacting 2026-07-20 16:38:21 +07:00
semantic-release-bot 54927f692f chore(release): 1.18.0 [skip ci]
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)

### Features

* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **agent:** implement agent execution engine and turn handling with background processing ([efcd191](https://github.com/asepharyana/zesdex/commit/efcd191f9698023944f9096a86c1efa47e412b6b))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
2026-07-20 09:33:26 +00:00
asepharyana efcd191f96 feat(agent): implement agent execution engine and turn handling with background processing 2026-07-20 16:29:39 +07:00
semantic-release-bot 2e8a4f2443 chore(release): 1.18.0 [skip ci]
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)

### Features

* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
2026-07-20 09:20:09 +00:00
asepharyana 2f5f62ab09 feat(tui): add rich context information including active jobs, README snippet, and recent git history 2026-07-20 16:16:26 +07:00
asepharyana 919435eb84 feat(tui): integrate rich context builder into agent turn process 2026-07-20 16:16:26 +07:00
asepharyana bed9f8cff6 feat(tui): enhance system prompt with workspace structure information 2026-07-20 16:16:26 +07:00
asepharyana 6c7995f5f5 feat(llm): increase max retries for streaming requests from 3 to 10
refactor(shell): remove unused debug logging from tracing imports
2026-07-20 16:16:26 +07:00
asepharyana 7ea226509c feat(llm): improve UTF-8 handling in response processing to prevent infinite loops
feat(shell): enhance output capturing by using threads for stdout and stderr
feat(tui): update usage widget to display token counts and provider/model information
refactor(tui): simplify status bar rendering by removing unnecessary token calculations
2026-07-20 16:16:26 +07:00
semantic-release-bot 6bef4a3f82 chore(release): 1.18.0 [skip ci]
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)

### Features

* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
2026-07-20 08:57:39 +00:00
asepharyana 16494d4b1e Enhance logging and documentation across utility tools and TUI overlays
- Added tracing instrumentation and improved logging messages in the Pong, Todofinish, and Todowrite tools for better debugging and monitoring.
- Enhanced documentation comments for clarity on tool functionalities and workflows.
- Implemented tracing in WorkflowRun, NoteFinding, ReadFindings, and HiveMind tools to track execution phases and findings.
- Updated TUI overlays (e.g., Bash, Clear Confirm, Editor, Effort Level, Help, Key Input, Learning, Loading, MCP, Model Selector, Plan, Quit Confirm, Rewind, Settings, Todo, Usage) with debug logging to capture rendering details.
- Improved the status bar and workflow panel rendering with additional debug information.
- Added tracing to various utility functions to facilitate better performance monitoring and error tracking.
2026-07-20 15:53:43 +07:00
semantic-release-bot f84dfb8476 chore(release): 1.18.0 [skip ci]
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)

### Features

* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
2026-07-20 08:25:10 +00:00
asepharyana 07b217cf4a feat(tui): enhance agent turn handling by grouping parameters and improving message management 2026-07-20 15:21:23 +07:00
asepharyana 9bfb95d795 feat(llm): improve tool call handling by dynamically resizing tool_calls and updating arguments 2026-07-20 14:58:39 +07:00
asepharyana 285dbb14cc feat(tui): add support for reasoning in chat messages and update transcript handling 2026-07-20 14:56:03 +07:00
asepharyana cac6626586 feat(tui): implement streaming support for LLM responses and update transcript handling 2026-07-20 14:51:16 +07:00
asepharyana 5a373d1031 feat(tui): update system message for clarity and conciseness in tool usage instructions 2026-07-20 14:47:55 +07:00
asepharyana fe2e916937 feat: add test_load and test_parse binaries for configuration loading and parsing 2026-07-20 14:47:55 +07:00
asepharyana 66ac4dbf02 feat: remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide 2026-07-20 14:47:55 +07:00
semantic-release-bot 44c3dd1239 chore(release): 1.18.0 [skip ci]
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)

### Features

* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
2026-07-20 06:56:23 +00:00
asepharyana 87abe8c335 feat(tui): optimize performance by caching display lines and token counts, and improve action handling 2026-07-20 13:52:20 +07:00
asepharyana 148ba4e07b feat(mcp): enhance MCP server registration with error handling and improve transport process management
refactor: update various tools for better error handling and path resolution
fix: improve markdown rendering and input display in TUI
2026-07-20 13:12:04 +07:00
210 changed files with 15660 additions and 16399 deletions
+94
View File
@@ -0,0 +1,94 @@
---
name: commit-and-push
description: Enforce Conventional Commits specification for all commit messages and push workflows
---
# Commit and Push Rule
All commits MUST follow the [Conventional Commits v1.0.0](https://www.conventionalcommits.org/en/v1.0.0/) specification. No exceptions.
## Commit Message Format
```
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
```
## Types
| Type | When to use |
|------|-------------|
| `feat` | New feature — correlates with `MINOR` in semver |
| `fix` | Bug fix — correlates with `PATCH` in semver |
| `chore` | Maintenance, deps, config — no production code change |
| `docs` | Documentation only |
| `style` | Formatting, whitespace — no logic change |
| `refactor` | Code restructure — no feature or fix |
| `perf` | Performance improvement |
| `test` | Adding or correcting tests |
| `build` | Build system or external dependency changes |
| `ci` | CI configuration and scripts |
| `revert` | Reverts a previous commit |
## Breaking Changes
Append `!` after type/scope to indicate a breaking change. This correlates with `MAJOR` in semver.
```
feat(api)!: remove deprecated /v1/users endpoint
BREAKING CHANGE: /v1/users has been removed. Use /v2/users instead.
```
A `BREAKING CHANGE:` footer can also be used in the commit body.
## Rules
1. Type is ALWAYS lowercase.
2. Description is imperative mood ("add", not "added" or "adds").
3. Description is lowercase, no trailing period.
4. Keep subject line under 72 characters.
5. Scope is optional but recommended for `feat` and `fix` — use the affected module name.
6. One logical change per commit. If a commit spans multiple types, split into multiple commits.
7. Body wraps at 72 characters. Use it to explain **why**, not **what**.
8. Footer uses `git trailer` format (e.g., `BREAKING CHANGE:`, `Reviewed-by:`, `Refs:`).
## Lefthook Hooks
Every commit and push MUST go through Lefthook's `pre-commit` and `pre-push` hooks. Hooks are the gatekeeper — if they fail, the commit/push does not happen.
1. `pre-commit` runs lint-staged on staged files. Commit is blocked until lint-staged passes.
2. `pre-push` runs lint-staged diff check and version bump. Push is blocked until both pass.
3. If a hook fails, **fix the root cause**. Do not work around it.
## Push
1. Every push MUST pass pre-commit and pre-push hooks (see `push-flow-convention` skill).
2. **NEVER use `--no-verify`** to bypass hooks. No exceptions. No "just this once." If hooks fail, fix the issue and retry.
3. **NEVER use `git commit --no-verify`**. If pre-commit fails, fix linting/formatting and restage.
4. **NEVER use `git push --no-verify`**. If pre-push fails, fix the failing check and push again.
5. Commit message quality is enforced — reject vague messages like "fix stuff", "update", "wip", "misc".
6. If Lefthook is not installed, run `pnpm exec lefthook install` before committing. Do not commit without hooks registered.
7. **NEVER add `Co-Authored-By` trailers for AI tools** (e.g., `Co-Authored-By: Claude Code <noreply@anthropic.com>`). Commits are authored by humans only. No AI attribution in commit messages.
8. **NEVER stage all files in one commit** (`git add .` or `git add -A` then commit). Group related changes into separate, focused commits. Each commit = one logical change. If a feature touches auth + billing, split into separate commits per module.
9. Stage files deliberately by name (`git add src/auth/login.ts src/auth/types.ts`). Review what's staged before committing (`git status`, `git diff --cached`).
## Examples
```
feat(auth): add google oauth sign-in
fix(cart): correct total calculation when discount is zero
chore: update eslint config
docs: add setup guide to README
refactor(billing): extract invoice calculation to service layer
test(users): add unit tests for avatar upload
perf(api): cache user profile queries
feat(api)!: change response format for /orders endpoint
```
## Reference
Full specification: [conventionalcommits.org/en/v1.0.0](https://www.conventionalcommits.org/en/v1.0.0/)
+58
View File
@@ -0,0 +1,58 @@
---
name: docs-folder
description: Route non-hexagonal files to docs/ folder to keep domain architecture clean
---
# Docs Folder Rule
Any file that does not fit the hexagonal architecture design pattern MUST live in the `docs/` folder. The source tree stays clean — only hexagonal-compliant code belongs in `src/`.
## Hexagonal Architecture Recap
```
src/
├── domain/ # Pure business logic, entities, value objects, ports (interfaces)
├── application/ # Use cases, orchestration, input/output ports
├── infrastructure/ # Adapters — DB, HTTP clients, messaging, external APIs
└── interfaces/ # Controllers, routes, CLI, resolvers (driving adapters)
```
Only code that fits one of these layers belongs in the source tree.
## What goes in `docs/`
| Item | Why it's not hexagonal |
|------|----------------------|
| Architecture decision records (ADRs) | Documentation, not code |
| API documentation / OpenAPI specs | Reference material |
| Database diagrams / ERDs | Design artifacts |
| Flowcharts / sequence diagrams | Visual documentation |
| Meeting notes / technical decisions | Project context |
| Onboarding guides | People documentation |
| RFC / proposal documents | Decision records |
| Scratch files / experiments | Not production code |
| Third-party integration guides | Reference material |
| Deployment runbooks | Ops documentation |
| Configuration examples / templates | Not domain logic |
| Migration guides / upgrade notes | Process documentation |
## Structure
```
docs/
├── adr/ # Architecture Decision Records
├── api/ # API specs, OpenAPI/Swagger files
├── diagrams/ # ERDs, flowcharts, sequence diagrams
├── guides/ # Onboarding, deployment, migration guides
├── rfcs/ # Proposals and RFCs
└── notes/ # Meeting notes, scratch, experiments
```
## Non-negotiables
1. NEVER put documentation files in `src/` — they pollute the domain.
2. NEVER put scratch code, experiments, or spikes in `src/` — use `docs/notes/` or a separate branch.
3. NEVER put config examples or templates in `src/` — use `docs/` or project root.
4. If a file doesn't implement a port, adapter, use case, or entity — it doesn't belong in `src/`.
5. Keep `docs/` organized by category, not by date or author.
6. README at project root is fine — detailed docs go in `docs/`.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,144 @@
# Clean Architecture
When to load this reference: when structuring a new service or module, drawing boundaries between components, deciding what a microservice should own, untangling framework coupling, reviewing a system for testability and longevity, or choosing a top-level folder structure.
Clean Architecture is Uncle Bob's synthesis of Hexagonal Architecture (Alistair Cockburn), Onion Architecture (Jeffrey Palermo), DCI (Coplien & Reenskaug), and BCE (Ivar Jacobson, *Object-Oriented Software Engineering*, 1992). They differ in detail but agree on one goal: **separation of concerns by layering**, with business rules isolated from delivery mechanisms.
The foundational insight comes from Jacobson: **architectures are structures that support the use cases of the system.** Not frameworks. Not databases. Not UIs. Use cases.
---
## What a Clean Architecture Produces
A system that is:
1. **Independent of frameworks.** Frameworks are tools, not constraints.
2. **Testable.** Business rules tested without UI, DB, web server, or any external element.
3. **Independent of UI.** The UI can be replaced (web → console → CLI → TUI) without touching business rules.
4. **Independent of database.** Swap PostgreSQL for MongoDB, ClickHouse, or in-memory without rewriting domain logic.
5. **Independent of any external agency.** The core business rules know nothing about the outside world.
**The database is a detail.** So is the web. So is the framework. These are the most common sources of architectural rot because developers mistake them for foundations.
> "The database is merely an IO device. It happens to provide some useful tools for sorting, querying, and reporting but those are ancillary to the system architecture." — *A Little Architecture* (2016)
---
## The Dependency Rule
The one rule that makes everything else work:
> **Source code dependencies point only inward, toward higher-level policy.**
- Nothing in an inner layer may name anything from an outer layer — no function, class, variable, or data format.
- Data formats convenient for the outer layer (ORM row struct, JSON DTO) must not leak inward.
- Control flow may cross boundaries in either direction, but *source dependencies* point only inward. The Dependency Inversion Principle (see [solid.md](solid.md)) is the mechanism that makes this possible when control flow runs outward.
When this rule is obeyed, external details — databases, frameworks, UIs — become replaceable plugins.
---
## The Four Concentric Layers
Schematic. You may need more or fewer for a given system, but the Dependency Rule always applies.
### 1. Entities (innermost)
Encapsulate **enterprise-wide** business rules. An entity can be a class with methods or a data structure plus functions — style choice.
- Entities know nothing about applications, use cases, frameworks, or anything outside.
- For single applications (no "enterprise"), these are your core business objects.
- These are the least affected by operational change. Changes to page navigation, auth mechanisms, or DB schemas must not reach here.
### 2. Use Cases
Encapsulate **application-specific** business rules. Use cases orchestrate entities to accomplish the application's goals.
- A use case directs entities; it does not contain enterprise-wide rules itself.
- Changes to the application's *behavior* land here. Changes to externalities do not.
- Simple request/response data structures (not entities) flow in and out.
### 3. Interface Adapters
Convert data between the format convenient for use cases/entities and the format convenient for external agencies.
- MVC's Controllers, Presenters, and Views live here.
- All SQL lives here (if the database is SQL). Nothing inside knows about SQL.
- DTOs are translated into domain types and back here.
### 4. Frameworks and Drivers (outermost)
The web framework, the database, the message broker, the file system. Glue code only — you do not write much application logic here. Details live here because details change, and the outer ring is where change is cheap.
---
## Crossing Boundaries
When control flow needs to run outward — a use case needs to call a presenter — a direct call violates the Dependency Rule (the inner layer names something in the outer layer).
**Solution: the Dependency Inversion Principle.** The use case calls an interface (an "output port") defined in its own layer. The outer-layer presenter implements that interface. Control flows outward; source dependencies point inward. Same pattern works for repositories, gateways, any outward call.
---
## What Crosses Boundaries
Only **simple data structures** cross boundaries:
- Plain structs or Data Transfer Objects.
- Primitive arguments in function calls.
- Maps/dictionaries, when appropriate.
Never pass Entity objects or ORM row objects across boundaries — that couples layers. Translate to the format most convenient for the inner circle at every boundary crossing.
---
## Screaming Architecture
From the 2011 blog post of the same name. The top-level layout of a project should *scream* what the system does, not what framework it uses.
**The blueprint metaphor.** Imagine looking at the blueprints of a building. A single-family residence: front entrance, foyer, living room, dining room, kitchen. A library: grand entrance, check-in clerks, reading areas, galleries of bookshelves. A shopping mall: corridors, store bays, parking lots. You can tell what kind of building it is before you see any sign.
What does *your* application architecture scream?
**Bad top-level:** `controllers/`, `models/`, `views/`, `services/`. Tells you the system uses MVC. Tells you nothing about what the system is for.
**Good top-level:** `billing/`, `shipping/`, `catalog/`, `fraud_detection/`. Now you know what the system does.
**Why it matters:** A good architecture lets you defer decisions about Rails, Spring, Hibernate, Tomcat, MySQL, or React until much later in the project. A framework-centric top-level locks those decisions in day one, and also makes the code base mute about its own purpose. The web is a *delivery mechanism*; the database is a *detail*. Neither should dominate your system structure.
If a stranger cannot tell from the directory structure whether they are looking at an e-commerce platform or a hospital records system, the architecture is failing at the highest level.
---
## Component Principles
Once modules are organized, they group into **components** — independently deployable units (libraries, services, jars, crates). Two sets of principles govern them.
### Component Cohesion
- **REP — Reuse/Release Equivalence Principle.** The unit of reuse is the unit of release.
- **CCP — Common Closure Principle.** Group together classes that change for the same reasons at the same times. (SRP at component scale.)
- **CRP — Common Reuse Principle.** Classes used together belong together; classes not used together don't. (ISP at component scale.)
These three pull in different directions — the **tension diagram** is a triangle and component design is an ongoing balance. Early-stage projects lean toward REP+CCP (ship quickly, include more); mature, widely-reused components shift toward CRP (exclude what clients don't need).
### Component Coupling
- **ADP — Acyclic Dependencies Principle.** The dependency graph among components must have no cycles. Break cycles with DIP or by extracting a new component both sides depend on.
- **SDP — Stable Dependencies Principle.** Depend in the direction of stability.
- **SAP — Stable Abstractions Principle.** Stable components should be abstract; volatile components should be concrete.
---
## Applying This in Practice
- **"NO DB" and "NO Web" are valid starting positions.** Business rules should be expressible, testable, and useful before either is chosen.
- **Frameworks are tools, not partners.** Wrap them. Keep `import django` or `import axum::Router` out of the core. (Uncle Bob's 2014 "Framework Bound" is a full rant on this.)
- **Not every project needs four full circles.** Small projects may collapse Entities and Use Cases into one layer. The Dependency Rule still applies whatever the count.
- **The seams matter most.** Architecture lives at the boundaries between components. Defend them at every review — once they rot, replacing a dependency stops being a weekend task and becomes a six-month project.
- **Dialog from *A Little Architecture* (2016).** An aspiring architect says they want to make decisions about databases, frameworks, and webservers. Uncle Bob's response: "Oh. Well, then you don't want to become a Software Architect after all." The architect's job is to make decisions that let you **defer** the irrelevant decisions.
---
## Architecture and Agility
From "The Scatology of Agile Architecture" (2009): Agile does *not* mean no up-front architecture. The myth that you evolve architecture from zero is, in Uncle Bob's words, "horse shit." Good teams do enough architecture up front to get the seams right, then let the details emerge inside those seams. See [craft.md](craft.md) for more on this.
@@ -0,0 +1,135 @@
# The Craftsmanship Ethic
When to load this reference: when the task raises questions of professional judgment — estimation, deadline pressure, sloppy code accumulating, pairing, saying no to bad requests, or when the user invokes "technical debt" or "mess" or "craftsmanship."
The behaviors in *Clean Code* and *Clean Architecture* are not ends in themselves. They are instrumental to a larger ethic that Uncle Bob has been refining since the early 2000s: the software craftsmanship movement, which evolved into the Programmer's Oath (see [oath.md](oath.md)) and the 2022 book *Clean Craftsmanship*. This reference captures the non-code parts of that ethic that still materially affect how Claude should behave when writing or reviewing code.
---
## Clean Code Is a Practice, Not a Destination
From many posts, consolidated:
- Every function is an opportunity to practice. You don't reach "clean" and stop.
- The **Boy Scout Rule** is the daily discipline: leave each module cleaner than you found it, even if just by renaming one variable.
- "The only way to go fast is to go well." Dirty code does not trade speed for quality; it trades illusory short-term speed for enormous long-term slowness. This is the Productivity Roller-Coaster: feel fast for weeks, slow to a crawl over months.
- From *Going Fast*: "Fast" is a property you get by being disciplined, not by skipping discipline.
- From *Speed Kills*: conversely, the illusion that you can get fast by cutting corners almost always kills a project.
---
## A Mess Is Not Technical Debt
**This distinction matters.** People conflate them, and the conflation is a way to make sloppiness sound respectable.
**Ward Cunningham's Technical Debt (the original, 1992):** a **deliberate, considered** engineering trade-off when a schedule or learning situation justifies using a suboptimal design temporarily. You know what the right design is; you are choosing the wrong one now, *with intent*, and you will fix it later. Example: initial website uses server-rendered pages because there's no time to build an Ajax framework.
**A Mess:** bad code written by someone who did not do the work to understand the problem, did not refactor, did not test, did not think. It is not "debt" because it was never a considered choice — it is just poor craftsmanship.
From "A Mess is not a Technical Debt" (2009): calling a mess "technical debt" launders bad craftsmanship as if it were responsible engineering. It is not. When refusing to ship a mess, do not accept the framing that "we're just taking on some debt." Debt is deliberate; a mess is sloppy.
**Fowler's four quadrants of debt** (prudent/imprudent × deliberate/inadvertent) are a better map:
- Deliberate+prudent: the original Cunningham case ("we must ship now, we'll fix X next sprint").
- Deliberate+imprudent: "we don't have time for design" (toxic, not actually debt).
- Inadvertent+prudent: "now I know how we should have done it" (honest learning).
- Inadvertent+imprudent: plain-old-mess masquerading as debt.
---
## Saying No
From "Saying No!" (2009) and elaborated in *The Clean Coder*: professionals have an obligation to refuse impossible or unethical demands.
- When a manager asks for something that cannot be done correctly in the time allowed, the professional answer is "no, but here's what I can do," not "yes" followed by silent quality compromise.
- "Yes and then failing to deliver" is worse than "no" — the manager loses the ability to plan around reality.
- Professionals push back on their own estimates. If pressure makes you shorten a number you believe, you have stopped being the expert the organization pays you to be.
Applied to Claude: when a user asks for something that cannot be done well under the stated constraints (skip the tests, skip the error handling, ship something that will crash), the right response includes the pushback. Offer what you *can* deliver cleanly, not a degraded version of what was asked for.
---
## Honest Estimates
From "Why is Estimating so Hard?" (2012) and related posts:
- Estimates are **probability distributions, not numbers.** Give a range: optimistic, nominal, pessimistic. Three-point estimates are honest; single-point estimates almost always compress uncertainty.
- "I don't know yet, let me do a spike" is a professional answer. "I'll have it by Friday" said under duress without real confidence is not.
- An estimate is not a commitment; commitments come from negotiating after estimates are honestly given.
---
## On Documentation
**Martin's First Law of Documentation** (from *Agile Software Development: PPP*): "Produce no document unless its need is immediate and significant."
This is often misread as "Agile means no documentation." It does not. From the butunclebob.com wiki:
> "Agile Development is NOT development without documentation. Rejecting documentation in the name of 'Agility' is a flawed religious behavior. It is just as flawed as uncritically accepting the production of dozens of different documents."
Documentation, like any engineering activity, is prioritized by ROI. Create documents that more than pay back the effort to produce them. Skip documents written because policy requires them but no one will read them.
What counts as documentation:
- API docs (rustdoc, TSDoc, javadoc) — high value, close to code.
- Architecture decision records (ADRs) — capture *why* decisions were made.
- Onboarding / how-to guides — pay back every time a new person joins.
- Specs for important flows — pay back every time a flow breaks.
What does not:
- Status reports that recapitulate information already in the tracker.
- Design documents written after implementation that no one will read.
- Comments that restate the code.
---
## Pairing Guidelines
From "Pairing Guidelines" (2021) and earlier posts:
- Pairing is a **tool**, not a religion. Use it when it works; don't when it doesn't.
- Mature agile teams pair maybe 5070% of the time, not 100%.
- Some problems require "time, focus, and silence" to study before attacking. Pairing on those is worse than solo.
- Pair at the start of a story to align direction; solo for deep-focus passages; reunite to review.
- The strategy "separate the syntax issues from the semantic issues" is a useful pattern when stuck as a pair — refactor the mechanical noise (parsing, config, regex) into a helper module so the core algorithm can be reasoned about on its own.
---
## Shipping Under Pressure
From "AgilePeopleStillDontGetIt" (2006) and "We must ship now and deal with consequences" (2009):
- "It is completely unacceptable to release code that you aren't sure works. Either make sure it works, or don't ship it. Period."
- "A feature that crashes is much worse than a feature that doesn't exist. A feature that doesn't exist will defer revenue. A feature that crashes makes enemies out of customers."
- "Our customers interpret features as promises. When we release a feature we are promising that it works. When it crashes we have broken that promise."
- "Shipping untested software is shipping something unfinished and your customers will force you to finish it. The pressure will be higher at orders of magnitude if you finish it AFTER you have shipped it."
Applied to Claude: when asked to ship quickly and drop tests, the honest response is that the tests aren't slowing you down; they are the only way to ship correctly. "Going fast" without tests produces code that will return tenfold in debugging and firefighting over the next weeks.
---
## Professionalism Is Not Rigid Formalism
From "Why the sea is boiling hot" (2009) — the closing statement of Uncle Bob's 2009 Rails Conf keynote:
> "Professionalism does not mean rigid formalism. Professionalism does not mean adhering to bureaucracy. Professionalism is **honor**. Professionalism is being honest with yourself and disciplined in the way you work. Professionalism is not letting fear take over."
Honor and discipline. Not process for its own sake. The rules in this skill are tools for being disciplined; they are not a rulebook to hide behind.
---
## The Tricky Bit
From "The Tricky Bit" (2010): a British MP flew the Concorde and complained to the designer that going supersonic "didn't feel any different at all." The designer beamed: "Yes, that was the tricky bit."
Clean code, good architecture, solid tests — when they are working, the reader doesn't notice. The absence of friction is the product. Code that *announces* how clever it is, how much architecture it has, how sophisticated its patterns are, is usually the opposite of clean. The goal is invisibility — the reader moves through the code and feels nothing but understanding.
---
## When Claude Should Invoke Any of This
- **User wants to skip tests "just this once":** reference the "A Mess is not Debt" framing and the shipping-under-pressure material.
- **User wants a speculative number instead of a range:** offer a range and explain why.
- **User wants you to document something they won't read:** suggest the minimum viable doc that pays its way.
- **User wants a "quick fix" that you can see will rot the module:** explain the Boy Scout Rule cost — a quick fix that makes the code worse is a negative-value change even at zero time cost.
- **User says "we're doing Agile, we don't write documentation":** redirect to Martin's First Law and the "it's about ROI" framing.
The oath ([oath.md](oath.md)) captures the promises. This file captures the attitude and the vocabulary for navigating the hard conversations where craft meets pressure.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,161 @@
# Programming Paradigms
When to load this reference: when choosing between procedural and OO style, writing code in a functional language, refactoring switch statements, handling persistence, or when the user asks about OO vs FP, design patterns, or Clean Code's chapter on objects and data structures.
Uncle Bob's reductionist framing of the three paradigms is a powerful lens for reasoning about code shape. Each paradigm imposes **discipline** by **taking something away** from the programmer.
---
## The Three Paradigms
Each paradigm is defined by what it *forbids*, not by what it enables. This is Dijkstra-style reasoning: fewer primitives mean fewer ways to be wrong.
### Structured Programming
- **Forbids:** `goto` (direct transfer of control).
- **Provides:** Sequence, Selection (if/else), Iteration (while). Dijkstra proved any algorithm can be expressed with just these three.
- **Why:** Dijkstra's 1968 letter "Go To Statement Considered Harmful." Unrestricted `goto` makes programs impossible to reason about. Restricted control flow is provably correct for sequence, selection, iteration; not provably correct with arbitrary `goto`.
- **Status today:** Won so completely that most developers don't even realize they're using it. Modern languages don't have `goto` (or discourage it).
### Object-Oriented Programming
- **Forbids:** Raw function pointers / indirect transfer of control through unmanaged pointers.
- **Provides:** Polymorphism. The language manages the function pointers for you.
- **Why:** Raw function pointers (as in C) are correct but fragile — every caller must follow conventions every time. Polymorphism provides the same runtime capability through a disciplined mechanism: objects carry their own dispatch table, set up once when the object is created.
- **The reductionist core:** OO = polymorphism. Encapsulation, methods-bound-to-data, and simple inheritance exist in C and Pascal too. **What OO uniquely gives you is convenient polymorphism.** "OO without polymorphism is not OO."
### Functional Programming
- **Forbids:** Assignment / mutation of state.
- **Provides:** Referential transparency. Same inputs → same outputs, always, everywhere.
- **Why:** Shared mutable state is the source of most concurrency bugs and most "action at a distance" reasoning failures. Forbidding it means state changes are explicit and localized.
- **The reductionist core:** FP = referential transparency. Higher-order functions exist in OO languages too (Smalltalk, etc.). What FP uniquely gives you is the guarantee that a function call cannot change anything you didn't pass to it.
### Why "Three Paradigms" Matters
These are **orthogonal**, not competing. Each removes a different freedom:
| Paradigm | Discipline on | Mechanism |
|---|---|---|
| Structured | Direct transfer of control | No `goto` |
| OO | Indirect transfer of control | Polymorphism |
| FP | Assignment | Referential transparency |
A language can (and modern ones often do) impose all three disciplines at once. You can write OO code functionally, and you can apply SOLID inside a functional program.
---
## OO and FP Are Orthogonal, Not Exclusive
From Uncle Bob's 2014 and 2018 "FP vs OO" posts:
> "The principles of software design still apply, regardless of your programming style. The fact that you've decided to use a language that doesn't have an assignment operator does not mean that you can ignore the Single Responsibility Principle; or that the Open Closed Principle is somehow automatic."
And from his 2023 *Functional Classes* post: "Should you subdivide a functional program into classes the way you would an object oriented program? Yes. You should. Because the rules don't change just because you've chosen to use immutable data structures."
**A class, reductively:** "A group of cohesive and narrowly defined functions that operate on an encapsulated data structure. The functions may, or may not, be polymorphically deployed." This definition works in Clojure, Haskell, Rust, Java, TypeScript, Python.
**The design principles transcend paradigm:**
- SRP applies in Clojure (group functions by actor).
- OCP applies in Haskell (use abstraction, add type class instances).
- DIP applies anywhere there are modules.
- A "class" in the sense above is a cohesive namespace of related functions plus the data they operate on.
---
## Data/Object Anti-Symmetry
From Chapter 6 of *Clean Code* and elaborated in the 2019 blog post "Classes vs. Data Structures."
**Two definitions that complement each other:**
- **Object:** A set of functions that operate on **implied** data. Data exists but is hidden. Callers see only functions.
- **Data structure:** A set of data elements operated on by **implied** functions. Data is exposed. Functions exist but are not specified by the structure.
They are **diametric opposites**. You cannot fully be both.
### Consequences
- **DTOs are data structures, not objects.**
- **Database tables are data structures, not objects.**
- **"ORM" is a misnomer.** There is no mapping between database tables and objects. ORMs map tables to data structures. (This is not pedantic; it explains why ORMs have the smells they do.)
- **Polymorphism is the marker of objects.** When `shape.area()` dispatches dynamically to the Circle or Square implementation, you are doing OO. When `area(shape)` is a free function with `match shape { Circle => …, Square => … }`, you are doing procedural work.
### The Four Symmetry Rules
These tell you when to choose each style.
| | Add new FUNCTION | Add new TYPE |
|---|---|---|
| **Classes (OO)** | **Hard** — change every class | **Easy** — add one class |
| **Data structures (procedural)** | **Easy** — add one function | **Hard** — change every function |
**Choose by expected axis of change:**
- If you expect more new functions than new types → procedural style with data structures + functions (e.g., visitor pattern, pattern matching over enums, Clojure-style).
- If you expect more new types than new functions → OO style with classes and polymorphism.
- The **Visitor pattern** is procedural-style behavior over OO data — it bridges the two.
**In Rust specifically:** enums with `match` are procedural by this taxonomy (add a variant → every match must handle it); traits with implementations are OO (add an impl → no existing code changes). Neither is wrong; choose by axis of change. If new variants are rare and new operations are common, the enum wins. If new types are common, the trait wins.
---
## Polymorphism and if-else-switch
From "if-else-switch" (2021). A very common refactor:
**The pattern.** When you see an if/else chain or switch that branches by type or by "kind," replace it with:
1. A base class or interface with one method per case.
2. Concrete implementations, one per branch.
3. A **factory** that creates the right implementation based on the discriminator (this is where the if/else/switch ends up, condensed into one place).
4. The business logic calls the interface, never the discriminator.
**Runtime characteristics are identical.** If/else does a procedural lookup, switch uses a compiler-built jump table, polymorphic dispatch uses a vtable — similar performance.
**What you gain:**
- The high-level business code no longer transitively depends on every low-level case.
- Each case is its own named method, not an indented block within a branch.
- New cases = new classes (OCP).
- Independent deployment becomes possible: the high-level module and each implementation can live in separate components.
**When not to apply:** if the switch is small, stable, and not type-based (e.g., processing a small enum of flags in one place), leaving it as a switch is fine. The rule is "factor out switches on *type*," not "destroy every conditional."
---
## The Tell-Don't-Ask Style
Alan Kay's original OO conception: objects as cells in a biological system.
> "Neurons are tellers, not askers. Hormones are tellers, not askers. In biological systems, communication was half-duplex."
Instead of:
```
if account.getBalance() < amount:
throw InsufficientFunds
account.setBalance(account.getBalance() - amount)
```
Say:
```
account.withdraw(amount) // account decides if it can, and how
```
The caller stops interrogating state and deciding. The object owns the decision. This is what Law of Demeter is a weak shadow of — the deeper principle is that state should not leak out of objects.
---
## Loops and State Machines
From the 2020 "Loopy" post. Any program with nested loops can be refactored step-by-step into a Turing-style finite state machine, with tests passing at every step. This is a useful mental exercise: a nested loop is a state machine that a programmer wrote too compactly.
Practical takeaway: when a loop body is getting complex, consider extracting an explicit state (enum of states) and transitioning between them. Reads better than four nested `if`s; generalizes better; easier to test.
---
## Applying This in Practice
- **Default to OO + polymorphism** for business logic where types vary (entities, strategies, handlers). Polymorphism is the mechanism behind DIP, OCP, and Clean Architecture boundaries.
- **Default to data structures + free functions** for values, messages, and records that flow through the system. DTOs, events, API payloads, DB rows.
- **Keep the two species apart.** A "hybrid" that has both public fields and rich behavior usually gets the worst of both worlds.
- **FP is not an exception to SOLID.** Cohesion, SRP, DIP all still apply; you express them with namespaces, protocols, or type classes instead of classes.
File diff suppressed because one or more lines are too long
+177
View File
@@ -0,0 +1,177 @@
# Test Driven Development
When to load this reference: when writing new tests, reviewing tests, debugging brittle tests, dealing with legacy code that resists testing, or deciding on a testing strategy for a module.
Tests are the safety net that makes fearless refactoring possible. Without that net, every change is a gamble; with it, every change can be confident. Tests are also the most precise, executable documentation a system will ever have.
**Michael Feathers's definition of legacy code:** *Legacy code is code without tests.* Uncle Bob adopted this definition and it underpins the TDD practice.
---
## The Three Laws of TDD
1. **You are not allowed to write any production code unless it is to make a failing unit test pass.**
2. **You are not allowed to write any more of a unit test than is sufficient to fail — and compilation failures are failures.**
3. **You are not allowed to write any more production code than is sufficient to pass the one failing unit test.**
The loop is measured in seconds, not minutes. Write a line or two of test, see it fail, write a line or two of production, see it pass, repeat. This is the **nano-cycle**.
**Why these rules:**
- **Debugging time plummets** — you were never more than 60 seconds away from working code.
- **Tests are automatic documentation** that cannot fall out of sync with the system.
- **Design improves** because code written to be testable is naturally decoupled.
- **Refactoring becomes fearless** because the net catches regressions instantly.
This is double-entry bookkeeping for software. Every behavior is stated twice — once in the test, once in the code — and they must agree.
---
## F.I.R.S.T. — Clean Tests
Clean tests are:
- **Fast.** Slow tests will stop being run. If a suite takes 10 minutes, people will commit without running it. 15-minute CI feedback is too slow for the TDD loop.
- **Independent.** No test depends on another. Any test can run alone, in any order.
- **Repeatable.** Same result in every environment — laptop, CI, staging. If a test depends on the network, wall clock, or shared database, it is flaky and must be fixed.
- **Self-validating.** Pass or fail. No manual inspection.
- **Timely.** Written *just before* the production code they cover — not "when we have time."
Test code is first-class. Hold it to the same clarity bar as production code. When tests rot, production code rots.
---
## Canonical Test Definitions (First-Class Tests, 2017)
The industry has been sloppy about what "unit," "integration," "acceptance," etc. mean. Uncle Bob's proposed taxonomy:
- **Unit Test.** Written by a programmer, for a programmer. Ensures production code does what the programmer expected. Sometimes called **programmer test** or **micro-test**.
- **Acceptance Test.** Written by the business (or a BA/QA representing the business). Ensures production code does what the business expects. Sometimes called **customer test**.
- **Integration Test.** Written by architects or technical leads. Ensures a sub-assembly of system components operates correctly. **These are plumbing tests, not business-rule tests** — rules are already verified by unit and acceptance tests.
- **System Test.** An integration test for the whole integrated system.
- **Micro-test** (Mike Hill / @GeePawHill). A unit test at very small scope — tests a single function or small group.
- **Functional Test.** A unit test at larger scope, with mocks for slow components.
> "Integration tests do not test business rules. Those rules have already been tested, once by programmer (unit) tests, and again by customer (acceptance) tests. Integration tests test the plumbing and choreography of the components." — Uncle Bob (Twitter, 2019)
**Implication for Claude when writing tests:** Know which kind of test you are writing and don't couple it to the wrong kind. If you're asked to "add tests" for a pure function, write unit/micro tests. If you're asked to "test the API works end-to-end," that's integration/system. Don't test business rules in an integration test — the rules should already have unit tests.
---
## Test Structure
Use one of these structures; be consistent.
- **Arrange / Act / Assert** — set up context, perform action, check result.
- **Given / When / Then** — same thing in BDD vocabulary.
- **Build / Operate / Check** — same thing, different vocabulary.
One *concept* per test. Often one assertion, but "one concept" is the real rule — several assertions verifying the same behavior are fine.
### Test Naming
Name the test for what it verifies about behavior, not for the method. `returns_empty_list_when_given_empty_input` beats `test_filter_1`. If the name runs long, the test is probably doing more than one thing.
---
## Test Doubles — The Hierarchy
Adapted from Gerard Meszaros's *xUnit Patterns*, with Uncle Bob's gloss. Each is a degree of sophistication above the last.
- **Dummy.** Passed around but never used. Fills a parameter slot.
- **Stub.** Returns canned answers. No logic.
- **Spy.** A stub that records the calls it received.
- **Mock.** A spy with expectations built in: set up *before* the act, verified *after*. Fails if expected interactions didn't happen.
- **Fake.** A working implementation with production-unfit shortcuts — e.g., in-memory repo that stands in for a real database.
Pick the lowest-sophistication double that does the job. A mock where a stub would suffice adds coupling and fragility.
**Uncle Bob hand-rolls most of his Java mocks** ("Manual Mocking," 2009) rather than using mockito, to keep explicit control over ceremony. This is a taste preference, not a rule, but his reasoning (less magic, clearer test code) is worth knowing.
---
## Chicago vs. London (State-ism vs. Mockism)
Two schools of TDD.
- **Chicago / Classical / State-ist.** Test behavior through state. Exercise the object, assert on its final state (or collaborators' state). Minimal mocking. Less coupled to implementation detail.
- **London / Mockist.** Test behavior through interactions. Mock collaborators; assert on calls. More explicit about collaboration but more coupled to it.
**Practical guidance:** Use Chicago for value objects, algorithms, internal logic. Use London at **boundaries** — where the code coordinates external collaborators. Never mock what you own when you could exercise it directly; mock (or fake) what you do not own when the real thing would make the test slow or flaky.
---
## Fragile Tests
Tests that break without a real regression are worse than no tests — they train developers to ignore the suite. Known causes:
- **Interface sensitivity.** Tests break because a signature changed, not behavior. Often a sign of excessive mocking.
- **Behavior sensitivity.** Tests break because an unrelated behavior changed. A sign of poor isolation.
- **Data sensitivity.** Tests break because shared fixtures changed. Fix by making tests own their data.
- **Context sensitivity.** Tests pass locally, fail in CI. Remove environmental coupling: clock, network, filesystem, time zone.
- **Over-specification.** Tests assert on more than the behavior under test — internal call order, private fields, log output. Assert on what the *user of the code* would observe.
A fragile test is a design signal — usually a missing abstraction, a leaky boundary, or an over-eager mock.
"Skilled TDDers understand that neither micro-tests, nor functional tests, nor acceptance tests should be coupled to the implementation of the system." — *First-Class Tests* (2017)
---
## As Tests Get More Specific, Code Gets More Generic
Uncle Bob's formulation (2009): tests are specifications. As you add tests, the specifications grow more specific. To satisfy them all, the production code must grow more *generic*. This is the inverse relationship that drives TDD-induced good design — the code gets pushed toward abstractions that cover many cases rather than one.
---
## The Transformation Priority Premise (TPP)
When making a failing test pass, there is a natural ordering of changes, simpler before more complex. Prefer earlier transformations when more than one would work:
1. `{} → nil` — no code → returning nil
2. `nil → constant` — return a constant
3. `constant → variable` — replace constant with a variable
4. `statement → statements` — add another statement
5. `unconditional → if` — introduce a branch
6. `scalar → array` — move from a single value to a collection
7. `array → container` — move to a richer collection type
8. `statement → recursion` — replace a statement with recursion
9. `if → while` — replace a branch with iteration
10. `expression → function` — extract a function
11. `variable → assignment` — introduce mutation
Using lower-priority transformations earlier creates needless complexity; using higher-priority ones later often indicates a design that could be simpler. TPP is a tiebreaker, not a law — but it usually guides tests toward algorithms that generalize cleanly.
---
## The Cycles of TDD
TDD operates at multiple time scales simultaneously. Working at only one scale produces bad software.
- **Seconds (Red-Green-Refactor).** The nano-cycle.
- **Minutes (Specific-to-Generic).** Tests grow more specific; code grows more generic.
- **Tens of minutes (Boundary).** Periodically step back and ask whether the module is still well-factored. Extract. Rename. Regroup.
- **Hours (Architecture).** Once a day or so, step back further: are the component boundaries still correct? Does the Dependency Rule still hold?
- **Days (Acceptance).** Acceptance tests (at the feature/use-case level) close the loop with the business.
Skipping the larger cycles is the most common failure mode. Red-Green-Refactor religiously, but never step back to reconsider architecture, and you end up with a suite of fine-grained tests wrapped around a tangled ball of mud.
---
## Testing Across Architectural Boundaries
- **The test boundary** is a first-class part of architecture. Tests live outside the system they test.
- **Do not couple tests to UI frameworks or databases.** If a test needs a browser to exercise a use case, the boundary between use case and UI is broken.
- **Legacy code strategy** (Feathers). Find a seam — a place where behavior can be varied without modifying code. Write a characterization test at that seam to pin down current behavior. Refactor behind the pin. Repeat.
Uncle Bob's position on test placement: "Don't test through UIs. Don't test through web servers. Test as close to the code as you can." — *Testing Like the TSA* (2017)
---
## Common Pitfalls
- **Writing tests after the fact.** Produces tests that confirm whatever the code happens to do, including the bugs. Much lower value than TDD.
- **Slow test suites.** If any unit test takes more than a fraction of a second, isolate it. Keep the unit suite fast and run integration tests separately.
- **Mocking what you own.** Prefer real objects for your own code.
- **Testing implementation details.** Refactors then break tests without any real regression, and people conclude "TDD gets in the way of refactoring." It doesn't — the tests were just wrong.
- **Skipping refactor.** Red-Green-… is not TDD. The third step is where design emerges.
- **Over-coverage religion.** Uncle Bob's ratio for some project types: 20% test-first, 80% test-after is acceptable for controllers/models/views (per *Testing Like the TSA*, 2017). The three laws are guidance for the hottest logic in the system, not dogma for every trivial accessor.
@@ -0,0 +1,449 @@
---
name: kana-rust-backend-best-practice
description: Reference guide for building a Rust clean-architecture backend with Axum, SeaORM, Argon2, JWT, and sea-orm-migration. Use when scaffolding a new Rust service, adding a feature (domain + use-case + repository + handler), or reviewing Rust code against the axum-clean-architecture reference layout.
---
# Axum Clean Architecture Skill
Reference stack (see `../axum-clean-architecture`):
| Layer | Tech |
|---|---|
| HTTP framework | Axum 0.8 |
| ORM | SeaORM 1.1 (PostgreSQL via sqlx + rustls) |
| Migrations | sea-orm-migration |
| Auth | Argon2 (password hashing) + jsonwebtoken (JWT) |
| Validation | zod-rs (schema-driven, mirrors Zod) |
| Pagination | paginator-rs + paginator-sea-orm + paginator-axum |
| Observability | tracing + tracing-subscriber |
| Middleware | tower-http (CORS, TraceLayer) |
| Runtime | Tokio (full features) |
| Error handling | anyhow (app-level), typed domain errors |
---
## 0. Workspace layout
```
axum-clean-architecture/
├── Cargo.toml # workspace, resolver = "3"
├── apps/
│ ├── iam/ # core domain library (lib crate)
│ │ └── src/
│ │ ├── domain/ # entities, repository traits, domain errors
│ │ ├── application/ # use cases + port traits
│ │ ├── infrastructure/ # SeaORM repos + auth services
│ │ └── presentation/ # Axum handlers, DTOs, middleware, state
│ ├── gateway/ # binary — assembles router, runs server
│ └── bootstrap/ # binary — seeds permissions/roles/admin
├── .config/ # AppServer, database/env helpers
└── .migrations/ # sea-orm-migration crate
```
The `iam` app is a **library crate**. `gateway` and `bootstrap` depend on it.
---
## 1. Dependency rules (strictly enforced)
```
presentation → application → domain
infrastructure → domain (implements domain traits)
presentation → infrastructure (only to wire AppState)
```
- Domain has **zero** external crate dependencies beyond `uuid`, `chrono`.
- Use cases depend only on port traits — never on concrete infrastructure types.
- Presentation instantiates use cases from `AppState` on every request; use cases are not stored.
---
## 2. Domain layer
### Entity pattern
Plain Rust structs — no derives beyond what domain logic needs. No ORM annotations.
```rust
// domain/user/entity.rs
pub struct User {
pub id: Uuid,
pub email: String,
pub password_hash: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
pub struct NewUser {
pub id: Uuid,
pub email: String,
pub password_hash: String,
}
#[derive(Default)]
pub struct UserPatch {
pub email: Option<String>,
pub password_hash: Option<String>,
}
```
### Repository trait pattern
Use `impl Future` in trait methods (Rust 2024 edition, no `async_trait` needed).
Always `Send + Sync` on the trait.
```rust
// domain/user/repository.rs
pub trait UserRepository: Send + Sync {
fn find_by_id(&self, id: Uuid)
-> impl Future<Output = Result<Option<User>, RepositoryError>> + Send;
fn find_by_email(&self, email: &str)
-> impl Future<Output = Result<Option<User>, RepositoryError>> + Send;
fn create(&self, user: NewUser)
-> impl Future<Output = Result<User, RepositoryError>> + Send;
fn update(&self, id: Uuid, patch: UserPatch)
-> impl Future<Output = Result<User, RepositoryError>> + Send;
fn delete(&self, id: Uuid)
-> impl Future<Output = Result<(), RepositoryError>> + Send;
fn list(&self, params: &PaginationParams)
-> impl Future<Output = Result<PaginatorResponse<User>, RepositoryError>> + Send;
}
```
### Shared RepositoryError (lives in domain)
```rust
pub enum RepositoryError {
NotFound,
Conflict(String),
Database(String),
}
```
### Domain errors
Per-aggregate. `AuthError` lives in `domain/auth/errors.rs`:
```rust
pub enum AuthError {
InvalidCredentials,
EmailAlreadyExists,
UserNotFound,
PasswordHashFailed(String),
PasswordVerificationFailed(String),
TokenGenerationFailed(String),
InvalidToken(String),
RepositoryError(String),
}
```
---
## 3. Application layer
### Port traits (interfaces for external services)
```rust
// application/auth/ports/password.rs
pub trait PasswordService: Send + Sync {
fn hash(&self, password: &str)
-> impl Future<Output = Result<String, PasswordError>> + Send;
fn verify(&self, password: &str, hash: &str)
-> impl Future<Output = Result<bool, PasswordError>> + Send;
}
```
```rust
// application/auth/ports/token.rs
pub trait TokenService: Send + Sync {
fn generate_auth_tokens(&self, sub: &str)
-> impl Future<Output = Result<(String, String), TokenError>> + Send;
fn verify_access_token(&self, token: &str)
-> Result<String, TokenError>;
}
```
### Use case pattern
Generic over port traits and repository traits. Constructed in the handler, not stored.
```rust
// application/user/use_cases/create.rs
pub struct CreateUserCommand { pub email: String, pub password: String }
pub struct CreateUserUseCase<P, R> {
password_service: P,
user_repository: R,
}
impl<P: PasswordService, R: UserRepository> CreateUserUseCase<P, R> {
pub fn new(password_service: P, user_repository: R) -> Self { ... }
pub async fn execute(&self, cmd: CreateUserCommand) -> Result<User, AuthError> {
// 1. guard: check uniqueness
// 2. hash password via port
// 3. create domain entity with Uuid::new_v4()
// 4. persist via repository
// 5. log + return
info!(user_id = %user.id, "user created");
Ok(user)
}
}
```
### Use case naming convention
| File | Struct | Command/Query |
|---|---|---|
| `create.rs` | `CreateXxxUseCase` | `CreateXxxCommand` |
| `update.rs` | `UpdateXxxUseCase` | `UpdateXxxCommand` |
| `delete.rs` | `DeleteXxxUseCase` | `DeleteXxxCommand` |
| `detail.rs` | `XxxDetailUseCase` | `XxxDetailQuery` |
| `list.rs` | `ListXxxsUseCase` | takes `&PaginationParams` |
---
## 4. Infrastructure layer
### SeaORM repository implementation
```rust
// infrastructure/repository/user.rs
#[derive(Clone)]
pub struct SeaOrmUserRepository { db: DatabaseConnection }
// Convert ORM Model → domain entity here (not in domain)
impl From<Model> for User { ... }
// Map DbErr → RepositoryError
fn map_db_err(e: DbErr) -> RepositoryError {
match e {
DbErr::RecordNotFound(_) => RepositoryError::NotFound,
other => { error!(error = %other, "database operation failed"); RepositoryError::Database(other.to_string()) }
}
}
impl UserRepository for SeaOrmUserRepository {
async fn create(&self, user: NewUser) -> Result<User, RepositoryError> {
let model = ActiveModel {
id: Set(user.id),
email: Set(user.email),
password_hash: Set(user.password_hash),
created_at: Set(now),
updated_at: Set(now),
};
let inserted = model.insert(&self.db).await.map_err(|e| match e {
DbErr::Exec(ref msg) | DbErr::Query(ref msg)
if msg.to_string().contains("unique") =>
RepositoryError::Conflict("email already exists".into()),
other => RepositoryError::Database(other.to_string()),
})?;
Ok(User::from(inserted))
}
}
```
Pagination uses `paginator-sea-orm`:
```rust
let response = UserEntity::find()
.paginate_with(&self.db, params)
.await
.map_err(|e| RepositoryError::Database(e.to_string()))?;
let mapped: Vec<User> = response.data.into_iter().map(User::from).collect();
Ok(PaginatorResponse { data: mapped, meta: response.meta })
```
### Auth services
- `Argon2PasswordService`: uses `spawn_blocking` for CPU-bound hashing, `SaltString::generate(OsRng)`.
- `JwtTokenService`: stores `secret: Vec<u8>`, generates separate access/refresh tokens with a `type` claim. `verify_access_token` checks `claims.token_type == "access"`.
### SeaORM entities (ORM models)
Live in `infrastructure/repository/entities/`. One file per table. Junction tables (`user_role`, `role_permission`) have composite primary keys. Timestamps use `DateTimeWithTimeZone`.
---
## 5. Presentation layer
### AppState
Concrete types only — no trait objects. Cheap to clone because `DatabaseConnection` is internally Arc-backed.
```rust
#[derive(Clone)]
pub struct AppState {
pub password_service: Argon2PasswordService,
pub token_service: JwtTokenService,
pub user_repository: SeaOrmUserRepository,
pub role_repository: SeaOrmRoleRepository,
pub permission_repository: SeaOrmPermissionRepository,
}
```
Injected via `Extension(state)` on every handler. Use cases are constructed inside handlers.
### AppError
```rust
pub enum AppError { BadRequest(String), Unauthorized, Forbidden, NotFound, Conflict(String), Internal(String) }
impl IntoResponse for AppError { /* maps to HTTP status + JSON { "error": "..." } */ }
impl From<AuthError> for AppError { ... }
impl From<RepositoryError> for AppError { ... }
impl From<TokenError> for AppError { ... }
```
Internal errors are logged with `tracing::error!` before returning a generic 500 message.
### Handler pattern
```rust
#[instrument(skip_all, fields(actor = %actor.id, email = %req.email))]
pub async fn create(
Extension(state): Extension<AppState>,
Extension(actor): Extension<AuthenticatedUser>,
Json(req): Json<CreateUserRequest>,
) -> Result<(StatusCode, Json<UserResponse>), AppError> {
let use_case = CreateUserUseCase::new(
state.password_service.clone(),
state.user_repository.clone(),
);
let user = use_case.execute(req.into()).await?;
Ok((StatusCode::CREATED, Json(user.into())))
}
```
Rules:
- Always `#[instrument(skip_all, fields(...))]` on every handler.
- Use `?` to propagate `AppError` (via `From` impls).
- `201 CREATED` for `POST`, `204 NO_CONTENT` for `DELETE`, `200 OK` for everything else.
- Pagination handlers return `PaginatedJson<Dto>` via `paginator-axum`.
### DTO pattern
```rust
#[derive(Debug, Serialize, Deserialize, ZodSchema)]
pub struct CreateUserRequest {
#[zod(email)]
pub email: String,
#[zod(min_length(8), max_length(128))]
pub password: String,
}
impl From<CreateUserRequest> for CreateUserCommand { ... }
#[derive(Debug, Serialize)]
pub struct UserResponse { pub id: Uuid, pub email: String, pub created_at: DateTime<Utc>, pub updated_at: DateTime<Utc> }
impl From<User> for UserResponse { ... }
```
- Request structs: `Deserialize + ZodSchema`. Use `#[zod(...)]` for field-level validation.
- Response structs: `Serialize` only. Never expose `password_hash`.
- Conversions: `impl From<Request> for Command` and `impl From<DomainEntity> for Response`.
### Middleware
**Auth middleware** (`presentation/middleware/auth.rs`):
- Extracts `Bearer <token>` from `Authorization` header.
- Calls `state.token_service.verify_access_token(token)`.
- Inserts `AuthenticatedUser { id: Uuid }` into request extensions.
**Permission check** (`presentation/middleware/permission.rs`):
- Called inline from handlers: `ensure_permission(&state, &actor, "users:write").await?`.
- Queries `permission_repository.find_for_user(actor.id)` and checks by name.
### Router assembly
```rust
pub fn build_router(state: AppState) -> Router {
Router::new()
.nest("/auth", auth::router())
.nest("/me", me::router())
.nest("/users", user::router())
.nest("/roles", role::router())
.nest("/permissions", permission::router())
.layer(Extension(state))
}
```
Gateway nests the IAM router at `/api/v1/iam` and adds a health check at `/`.
---
## 6. Migrations (sea-orm-migration)
```rust
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.create_table(
Table::create()
.table(Users::Table)
.if_not_exists()
.col(ColumnDef::new(Users::Id).uuid().not_null().primary_key())
.col(ColumnDef::new(Users::Email).string().not_null().unique_key())
...
.to_owned(),
).await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.drop_table(Table::drop().table(Users::Table).to_owned()).await
}
}
#[derive(DeriveIden)]
pub enum Users { Table, Id, Email, PasswordHash, CreatedAt, UpdatedAt }
```
Naming convention: `m{YYYYMMDD}_{6-digit-seq}_{description}.rs`, e.g. `m20260413_000001_create_users.rs`.
---
## 7. Bootstrap pattern
A separate `bootstrap` binary seeds idempotent system data (permissions, roles, admin user):
```rust
// Check existence before inserting — fully idempotent
if permission_repo.find_by_name("rbac:manage").await?.is_none() {
permission_repo.create(...).await?;
}
```
Standard permissions:
- `rbac:manage`, `users:read`, `users:write`, `roles:read`, `roles:write`, `permissions:read`, `permissions:write`
---
## 8. Adding a new aggregate (checklist)
1. **Domain**: `domain/{name}/entity.rs` (entity + NewXxx + XxxPatch), `domain/{name}/repository.rs` (trait + errors), `domain/{name}/errors.rs` if needed.
2. **Application**: `application/{name}/mod.rs`, `application/{name}/use_cases/{create,update,delete,detail,list}.rs`.
3. **Infrastructure entity**: `infrastructure/repository/entities/{name}.rs` (SeaORM model).
4. **Infrastructure repo**: `infrastructure/repository/{name}.rs` (`From<Model>`, `impl XxxRepository for SeaOrmXxxRepository`).
5. **Add to AppState**: `{name}_repository: SeaOrmXxxRepository`.
6. **Presentation DTO**: `presentation/{name}/dto.rs` (Request + Response with `From` impls).
7. **Presentation handlers**: `presentation/{name}/handlers.rs` (`#[instrument]`, construct use case, return DTO).
8. **Presentation router**: `presentation/{name}/mod.rs` (define routes with `axum_route_macro` or `Router::new().route(...)`).
9. **Nest in `build_router`**.
10. **Migration**: new file in `.migrations/src/` following naming convention.
11. **Bootstrap**: seed any required initial data.
---
## 9. Key conventions
- Edition **2024** — use `impl Future` in traits, not `#[async_trait]`.
- All timestamps are `DateTime<Utc>` in domain; `DateTimeWithTimeZone` in SeaORM models; convert with `.with_timezone(&Utc)`.
- UUIDs generated with `Uuid::new_v4()` in the use case, not the repository.
- Unique-constraint conflicts detected via string match on `DbErr::Exec`/`DbErr::Query` containing `"unique"` — map to `RepositoryError::Conflict`.
- `tracing::instrument` on every handler; log user/actor IDs as structured fields.
- `warn!` for expected failures (wrong password, permission denied), `error!` for unexpected DB errors.
- Response structs never expose internal fields (`password_hash`, internal IDs from junction tables).
@@ -0,0 +1,107 @@
---
name: push-flow-convention
description: Enforce pre-commit/pre-push hooks, lint-staged checks, and semver version bump on every push
---
# Push Flow Convention
Every repository MUST enforce the same pre-commit, pre-push, and versioning flow. No push lands without hooks, lint-staged, and a version bump.
## Required Setup
### 1. Lefthook (pre-commit + pre-push)
> **Always use [Lefthook](https://lefthook.dev/) for git hooks. Never use husky.**
Install once per repo:
```bash
pnpm add -D lefthook lint-staged
pnpm exec lefthook install
```
Create `lefthook.yml` in project root:
```yaml
pre-commit:
commands:
lint-staged:
run: pnpm exec lint-staged
pre-push:
commands:
lint-staged:
run: pnpm exec lint-staged --diff="origin/{push_remote_branch}...HEAD"
bump:
run: pnpm run bump
```
### 2. lint-staged
Declared in `package.json`. Runs ONLY on staged files so commits stay fast.
```json
{
"lint-staged": {
"*.{ts,tsx,js,jsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md,yml,yaml}": [
"prettier --write"
]
}
}
```
### 3. Version bump script
`package.json` MUST expose a `bump` script used by `pre-push`:
```json
{
"scripts": {
"bump": "node scripts/bump-version.mjs"
}
}
```
The script inspects the diff between the current branch and its upstream, applies the semver rule below, and writes the new version back to `package.json`. Commit the bump before pushing (amend the previous commit or create a `chore: adjust package.json version (bump)` commit — see `commit-convention`).
## Semver Rules (applied on every push)
The bump is based on the changes in the commits being pushed:
| Change size / kind | Bump |
|--------------------|------|
| `< 5` changed files across pushed commits | **patch** (`x.y.Z`) |
| `>= 5` changed files across pushed commits | **minor** (`x.Y.0`) |
| New feature OR new behaviour (any `feat:` commit) | **major** (`X.0.0`) |
Rules in order of precedence:
1. If ANY commit being pushed is a `feat(...)`**major** bump.
2. Otherwise, count files changed (`git diff --name-only origin/<branch>...HEAD | wc -l`):
- fewer than 5 → **patch**
- 5 or more → **minor**
The `feat` rule always wins — a new feature is always a major bump regardless of file count.
## Non-negotiables
1. NEVER push without pre-commit and pre-push hooks installed.
2. NEVER bypass hooks with `--no-verify` — if a hook fails, fix the root cause.
3. NEVER push without a version bump. Every push = new version.
4. The bump commit MUST use the `chore: adjust package.json version (bump)` message (see `commit-convention`).
5. lint-staged MUST run on every commit. A green lint-staged is a prerequisite for the commit to be created.
6. If `pnpm` is not the package manager, substitute with `npm` or `yarn` but keep the same flow.
## Quick verification checklist
Before declaring the push flow set up, confirm:
- [ ] `lefthook.yml` exists with `pre-commit` and `pre-push` hooks
- [ ] `pnpm exec lefthook install` has been run (hooks registered in `.git/hooks/`)
- [ ] `package.json` has a `lint-staged` block
- [ ] `package.json` has a `bump` script
- [ ] A dry-run commit triggers lint-staged
- [ ] A dry-run push triggers the version bump
+1
View File
@@ -6,3 +6,4 @@ package.json
package-lock.json package-lock.json
.superpowers/ .superpowers/
docs/lesson/ docs/lesson/
.kilo/
+18
View File
@@ -0,0 +1,18 @@
{
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
["@semantic-release/exec", {
"prepareCmd": "sed -i 's/^version = \"[^\"]*\"/version = \"${nextRelease.version}\"/' Cargo.toml && cargo check"
}],
["@semantic-release/git", {
"assets": ["Cargo.toml", "CHANGELOG.md"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}],
["@semantic-release/github", {
"assets": []
}]
]
}
+38
View File
@@ -0,0 +1,38 @@
# AGENTS.md
This file provides guidance to Kilo when working with code in the zesdex repository.
## Best Practice Conventions
Zesdex follows Kana Engineering Best Practices:
1. **Clean Architecture** — Strict domain/application/infrastructure/presentation layering.
- Domain has ZERO framework dependencies.
- Application depends only on domain.
- Infrastructure implements domain traits.
- DTOs cross layer boundaries, NOT entities.
2. **Clean Code** — Functions under ~40 lines, one level of abstraction per function, descriptive names, no flag arguments, no commented-out code.
3. **Documentation** — Every pub fn, struct, enum, and trait needs a doc comment (///) explaining what, flow, why, and return value.
4. **Commit Convention** — Conventional Commits in Bahasa Indonesia: `feat(scope):`, `fix(scope):`, `chore:`, `docs:`.
5. **Error Handling**`anyhow::Result` and `anyhow::bail!` throughout. Log with `tracing` (never stderr).
6. **Testing**`#[cfg(test)] mod tests` blocks inline in production files. Tests are F.I.R.S.T. (Fast, Independent, Repeatable, Self-validating, Timely).
7. **No Compiler Bypasses** — Never use `#[allow(...)]`, `#[expect(...)]`, or `#[allow(dead_code)]`. Fix the underlying code.
8. **Boy Scout Rule** — Leave every module cleaner than you found it.
## Available Agents
- `@rust-engineer` — Rust clean architecture specialist (subagent).
- `@code-reviewer` — Code review specialist (subagent).
## Available Commands
- `/check` — Run cargo check, clippy, and tests.
- `/audit` — Code quality audit against clean-architecture best practices.
- `/doc` — Generate or update doc comments.
+353
View File
@@ -1,3 +1,356 @@
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-22)
### Bug Fixes
* add .releaserc.json for semantic-release configuration ([047d718](https://github.com/asepharyana/zesdex/commit/047d7183d72adaa0dd1a18a7f92b22d67d5aa85f))
* **clippy:** replace type annotation with type alias + .insert() to avoid trivial_cast ([ffefa8c](https://github.com/asepharyana/zesdex/commit/ffefa8c07facbba83778f426ca4715569556b08a))
* **rust:** remove unused imports, variables, and dead code causing CI build failures ([eb8cc17](https://github.com/asepharyana/zesdex/commit/eb8cc1799359a5ef5b4a4ee3a4202b87e80bfe6c))
* **rust:** resolve all clippy warnings treated as errors in CI ([6b90c7e](https://github.com/asepharyana/zesdex/commit/6b90c7eb0d17d9281a377454e76dcc88a4232bc7))
* **tui:** resolve remaining clippy errors in workspace ([eed4025](https://github.com/asepharyana/zesdex/commit/eed4025918de65eb2f3fd98cd3350d62f3322132))
### Features
* add semantic search tool for code symbol indexing and searching ([fef3c92](https://github.com/asepharyana/zesdex/commit/fef3c925cd02e1d76e2d990cc6fa4e4cba64fab0))
* Add SOLID principles and TDD reference documentation ([55677dd](https://github.com/asepharyana/zesdex/commit/55677dd67100813a5ed2b0bbf02257b57875ea07))
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **agent:** add AI summarization for conversation history compacting ([873f870](https://github.com/asepharyana/zesdex/commit/873f870e233dd0d2c6f0cce935f83fb8ce373505))
* **agent:** implement agent execution engine and turn handling with background processing ([efcd191](https://github.com/asepharyana/zesdex/commit/efcd191f9698023944f9096a86c1efa47e412b6b))
* **best_practice:** add code quality scanning and commit message validation ([8ecc588](https://github.com/asepharyana/zesdex/commit/8ecc588a3e125dc6a4b2e7eb4c12c6b344449c07))
* centralize default constants and refactor overlay enter handling in TUI ([d615090](https://github.com/asepharyana/zesdex/commit/d615090dcd34bdb93be22c1c7ce4ba674363c258))
* enhance context gathering in auto-review engine and agent runner ([dcc8c3e](https://github.com/asepharyana/zesdex/commit/dcc8c3ee42ef568843e04143048d799bc0882288))
* enhance explore phase with TUI workflow event handling ([b3a4d13](https://github.com/asepharyana/zesdex/commit/b3a4d131d13d94bd307720cb50c2d02dedf92873))
* implement Component trait for modular UI components and refactor TUI views to use it ([d59713d](https://github.com/asepharyana/zesdex/commit/d59713d3e3aa4325f9753bbddd068654e442f6ef))
* implement mandatory explore phase with parallel subagents ([f368f3a](https://github.com/asepharyana/zesdex/commit/f368f3a1c0d2df02eb50542a81bcff96d9cdc420))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* refactor auto-review engine to use spawn_subagent for improved thread handling ([094eb4b](https://github.com/asepharyana/zesdex/commit/094eb4b8baa5abd878dcf5b611cf615955bc79eb))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** introduce comprehensive state management for TUI interface ([8c58faf](https://github.com/asepharyana/zesdex/commit/8c58faf2920b7341dd50ace15044ff113cefb576))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-22)
### Bug Fixes
* add .releaserc.json for semantic-release configuration ([047d718](https://github.com/asepharyana/zesdex/commit/047d7183d72adaa0dd1a18a7f92b22d67d5aa85f))
* **rust:** remove unused imports, variables, and dead code causing CI build failures ([eb8cc17](https://github.com/asepharyana/zesdex/commit/eb8cc1799359a5ef5b4a4ee3a4202b87e80bfe6c))
* **rust:** resolve all clippy warnings treated as errors in CI ([6b90c7e](https://github.com/asepharyana/zesdex/commit/6b90c7eb0d17d9281a377454e76dcc88a4232bc7))
* **tui:** resolve remaining clippy errors in workspace ([eed4025](https://github.com/asepharyana/zesdex/commit/eed4025918de65eb2f3fd98cd3350d62f3322132))
### Features
* add semantic search tool for code symbol indexing and searching ([fef3c92](https://github.com/asepharyana/zesdex/commit/fef3c925cd02e1d76e2d990cc6fa4e4cba64fab0))
* Add SOLID principles and TDD reference documentation ([55677dd](https://github.com/asepharyana/zesdex/commit/55677dd67100813a5ed2b0bbf02257b57875ea07))
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **agent:** add AI summarization for conversation history compacting ([873f870](https://github.com/asepharyana/zesdex/commit/873f870e233dd0d2c6f0cce935f83fb8ce373505))
* **agent:** implement agent execution engine and turn handling with background processing ([efcd191](https://github.com/asepharyana/zesdex/commit/efcd191f9698023944f9096a86c1efa47e412b6b))
* **best_practice:** add code quality scanning and commit message validation ([8ecc588](https://github.com/asepharyana/zesdex/commit/8ecc588a3e125dc6a4b2e7eb4c12c6b344449c07))
* centralize default constants and refactor overlay enter handling in TUI ([d615090](https://github.com/asepharyana/zesdex/commit/d615090dcd34bdb93be22c1c7ce4ba674363c258))
* enhance context gathering in auto-review engine and agent runner ([dcc8c3e](https://github.com/asepharyana/zesdex/commit/dcc8c3ee42ef568843e04143048d799bc0882288))
* enhance explore phase with TUI workflow event handling ([b3a4d13](https://github.com/asepharyana/zesdex/commit/b3a4d131d13d94bd307720cb50c2d02dedf92873))
* implement Component trait for modular UI components and refactor TUI views to use it ([d59713d](https://github.com/asepharyana/zesdex/commit/d59713d3e3aa4325f9753bbddd068654e442f6ef))
* implement mandatory explore phase with parallel subagents ([f368f3a](https://github.com/asepharyana/zesdex/commit/f368f3a1c0d2df02eb50542a81bcff96d9cdc420))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* refactor auto-review engine to use spawn_subagent for improved thread handling ([094eb4b](https://github.com/asepharyana/zesdex/commit/094eb4b8baa5abd878dcf5b611cf615955bc79eb))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** introduce comprehensive state management for TUI interface ([8c58faf](https://github.com/asepharyana/zesdex/commit/8c58faf2920b7341dd50ace15044ff113cefb576))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-22)
### Bug Fixes
* add .releaserc.json for semantic-release configuration ([047d718](https://github.com/asepharyana/zesdex/commit/047d7183d72adaa0dd1a18a7f92b22d67d5aa85f))
* **rust:** remove unused imports, variables, and dead code causing CI build failures ([eb8cc17](https://github.com/asepharyana/zesdex/commit/eb8cc1799359a5ef5b4a4ee3a4202b87e80bfe6c))
### Features
* add semantic search tool for code symbol indexing and searching ([fef3c92](https://github.com/asepharyana/zesdex/commit/fef3c925cd02e1d76e2d990cc6fa4e4cba64fab0))
* Add SOLID principles and TDD reference documentation ([55677dd](https://github.com/asepharyana/zesdex/commit/55677dd67100813a5ed2b0bbf02257b57875ea07))
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **agent:** add AI summarization for conversation history compacting ([873f870](https://github.com/asepharyana/zesdex/commit/873f870e233dd0d2c6f0cce935f83fb8ce373505))
* **agent:** implement agent execution engine and turn handling with background processing ([efcd191](https://github.com/asepharyana/zesdex/commit/efcd191f9698023944f9096a86c1efa47e412b6b))
* **best_practice:** add code quality scanning and commit message validation ([8ecc588](https://github.com/asepharyana/zesdex/commit/8ecc588a3e125dc6a4b2e7eb4c12c6b344449c07))
* centralize default constants and refactor overlay enter handling in TUI ([d615090](https://github.com/asepharyana/zesdex/commit/d615090dcd34bdb93be22c1c7ce4ba674363c258))
* enhance context gathering in auto-review engine and agent runner ([dcc8c3e](https://github.com/asepharyana/zesdex/commit/dcc8c3ee42ef568843e04143048d799bc0882288))
* enhance explore phase with TUI workflow event handling ([b3a4d13](https://github.com/asepharyana/zesdex/commit/b3a4d131d13d94bd307720cb50c2d02dedf92873))
* implement Component trait for modular UI components and refactor TUI views to use it ([d59713d](https://github.com/asepharyana/zesdex/commit/d59713d3e3aa4325f9753bbddd068654e442f6ef))
* implement mandatory explore phase with parallel subagents ([f368f3a](https://github.com/asepharyana/zesdex/commit/f368f3a1c0d2df02eb50542a81bcff96d9cdc420))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* refactor auto-review engine to use spawn_subagent for improved thread handling ([094eb4b](https://github.com/asepharyana/zesdex/commit/094eb4b8baa5abd878dcf5b611cf615955bc79eb))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** introduce comprehensive state management for TUI interface ([8c58faf](https://github.com/asepharyana/zesdex/commit/8c58faf2920b7341dd50ace15044ff113cefb576))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)
### Bug Fixes
* add .releaserc.json for semantic-release configuration ([047d718](https://github.com/asepharyana/zesdex/commit/047d7183d72adaa0dd1a18a7f92b22d67d5aa85f))
### Features
* add semantic search tool for code symbol indexing and searching ([fef3c92](https://github.com/asepharyana/zesdex/commit/fef3c925cd02e1d76e2d990cc6fa4e4cba64fab0))
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **agent:** add AI summarization for conversation history compacting ([873f870](https://github.com/asepharyana/zesdex/commit/873f870e233dd0d2c6f0cce935f83fb8ce373505))
* **agent:** implement agent execution engine and turn handling with background processing ([efcd191](https://github.com/asepharyana/zesdex/commit/efcd191f9698023944f9096a86c1efa47e412b6b))
* enhance context gathering in auto-review engine and agent runner ([dcc8c3e](https://github.com/asepharyana/zesdex/commit/dcc8c3ee42ef568843e04143048d799bc0882288))
* implement Component trait for modular UI components and refactor TUI views to use it ([d59713d](https://github.com/asepharyana/zesdex/commit/d59713d3e3aa4325f9753bbddd068654e442f6ef))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* refactor auto-review engine to use spawn_subagent for improved thread handling ([094eb4b](https://github.com/asepharyana/zesdex/commit/094eb4b8baa5abd878dcf5b611cf615955bc79eb))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)
### Bug Fixes
* add .releaserc.json for semantic-release configuration ([047d718](https://github.com/asepharyana/zesdex/commit/047d7183d72adaa0dd1a18a7f92b22d67d5aa85f))
### Features
* add semantic search tool for code symbol indexing and searching ([fef3c92](https://github.com/asepharyana/zesdex/commit/fef3c925cd02e1d76e2d990cc6fa4e4cba64fab0))
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **agent:** add AI summarization for conversation history compacting ([873f870](https://github.com/asepharyana/zesdex/commit/873f870e233dd0d2c6f0cce935f83fb8ce373505))
* **agent:** implement agent execution engine and turn handling with background processing ([efcd191](https://github.com/asepharyana/zesdex/commit/efcd191f9698023944f9096a86c1efa47e412b6b))
* enhance context gathering in auto-review engine and agent runner ([dcc8c3e](https://github.com/asepharyana/zesdex/commit/dcc8c3ee42ef568843e04143048d799bc0882288))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* refactor auto-review engine to use spawn_subagent for improved thread handling ([094eb4b](https://github.com/asepharyana/zesdex/commit/094eb4b8baa5abd878dcf5b611cf615955bc79eb))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)
### Features
* add semantic search tool for code symbol indexing and searching ([fef3c92](https://github.com/asepharyana/zesdex/commit/fef3c925cd02e1d76e2d990cc6fa4e4cba64fab0))
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **agent:** add AI summarization for conversation history compacting ([873f870](https://github.com/asepharyana/zesdex/commit/873f870e233dd0d2c6f0cce935f83fb8ce373505))
* **agent:** implement agent execution engine and turn handling with background processing ([efcd191](https://github.com/asepharyana/zesdex/commit/efcd191f9698023944f9096a86c1efa47e412b6b))
* enhance context gathering in auto-review engine and agent runner ([dcc8c3e](https://github.com/asepharyana/zesdex/commit/dcc8c3ee42ef568843e04143048d799bc0882288))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* refactor auto-review engine to use spawn_subagent for improved thread handling ([094eb4b](https://github.com/asepharyana/zesdex/commit/094eb4b8baa5abd878dcf5b611cf615955bc79eb))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)
### Features
* add semantic search tool for code symbol indexing and searching ([fef3c92](https://github.com/asepharyana/zesdex/commit/fef3c925cd02e1d76e2d990cc6fa4e4cba64fab0))
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **agent:** add AI summarization for conversation history compacting ([873f870](https://github.com/asepharyana/zesdex/commit/873f870e233dd0d2c6f0cce935f83fb8ce373505))
* **agent:** implement agent execution engine and turn handling with background processing ([efcd191](https://github.com/asepharyana/zesdex/commit/efcd191f9698023944f9096a86c1efa47e412b6b))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* refactor auto-review engine to use spawn_subagent for improved thread handling ([094eb4b](https://github.com/asepharyana/zesdex/commit/094eb4b8baa5abd878dcf5b611cf615955bc79eb))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)
### Features
* add semantic search tool for code symbol indexing and searching ([fef3c92](https://github.com/asepharyana/zesdex/commit/fef3c925cd02e1d76e2d990cc6fa4e4cba64fab0))
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **agent:** add AI summarization for conversation history compacting ([873f870](https://github.com/asepharyana/zesdex/commit/873f870e233dd0d2c6f0cce935f83fb8ce373505))
* **agent:** implement agent execution engine and turn handling with background processing ([efcd191](https://github.com/asepharyana/zesdex/commit/efcd191f9698023944f9096a86c1efa47e412b6b))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)
### Features
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **agent:** add AI summarization for conversation history compacting ([873f870](https://github.com/asepharyana/zesdex/commit/873f870e233dd0d2c6f0cce935f83fb8ce373505))
* **agent:** implement agent execution engine and turn handling with background processing ([efcd191](https://github.com/asepharyana/zesdex/commit/efcd191f9698023944f9096a86c1efa47e412b6b))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)
### Features
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **agent:** implement agent execution engine and turn handling with background processing ([efcd191](https://github.com/asepharyana/zesdex/commit/efcd191f9698023944f9096a86c1efa47e412b6b))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)
### Features
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **llm:** improve UTF-8 handling in response processing to prevent infinite loops ([7ea2265](https://github.com/asepharyana/zesdex/commit/7ea226509c8e11ee1bab21d216e0413715988fb9))
* **llm:** increase max retries for streaming requests from 3 to 10 ([6c7995f](https://github.com/asepharyana/zesdex/commit/6c7995f5f5481c48ccd449e72d9563425fdcb837))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add rich context information including active jobs, README snippet, and recent git history ([2f5f62a](https://github.com/asepharyana/zesdex/commit/2f5f62ab097614d8b0c7a3e5c7bd364cd479e0aa))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** enhance system prompt with workspace structure information ([bed9f8c](https://github.com/asepharyana/zesdex/commit/bed9f8cff60711c4db998c8f10f32f8e210ae410))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** integrate rich context builder into agent turn process ([919435e](https://github.com/asepharyana/zesdex/commit/919435eb84f9a41a0637052a5dc1234b410714f8))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)
### Features
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)
### Features
* add test_load and test_parse binaries for configuration loading and parsing ([fe2e916](https://github.com/asepharyana/zesdex/commit/fe2e9169371bf7a3cc9391d8467d47fc2258ad1f))
* **llm:** improve tool call handling by dynamically resizing tool_calls and updating arguments ([9bfb95d](https://github.com/asepharyana/zesdex/commit/9bfb95d795d6c7cc10cfcb907642bba7b8bb7f19))
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* remove obsolete design documents for clipboard OSC52, diff view, file mention, context compaction, and add development guide ([66ac4db](https://github.com/asepharyana/zesdex/commit/66ac4dbf027820697f4303264666b5f63a0b31d4))
* **tui:** add support for reasoning in chat messages and update transcript handling ([285dbb1](https://github.com/asepharyana/zesdex/commit/285dbb14cccb1c261134c1fcb6c30ef1ed02bb55))
* **tui:** enhance agent turn handling by grouping parameters and improving message management ([07b217c](https://github.com/asepharyana/zesdex/commit/07b217cf4a81b99c63101512932935246fcbc7ae))
* **tui:** implement streaming support for LLM responses and update transcript handling ([cac6626](https://github.com/asepharyana/zesdex/commit/cac662658651ae092cffddfc88f39a775afef942))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
* **tui:** update system message for clarity and conciseness in tool usage instructions ([5a373d1](https://github.com/asepharyana/zesdex/commit/5a373d1031b476970bd45804a65c7455be99e78d))
# [1.18.0](https://github.com/asepharyana/zesdex/compare/v1.17.0...v1.18.0) (2026-07-20)
### Features
* **mcp:** enhance MCP server registration with error handling and improve transport process management ([148ba4e](https://github.com/asepharyana/zesdex/commit/148ba4e07b424736c6b4824c5786c0c7c9cbf0a1))
* **tui:** optimize performance by caching display lines and token counts, and improve action handling ([87abe8c](https://github.com/asepharyana/zesdex/commit/87abe8c3358ac17196df93d19169901a856850d3))
# [1.17.0](https://github.com/asepharyana/zesdex/compare/v1.16.1...v1.17.0) (2026-07-20) # [1.17.0](https://github.com/asepharyana/zesdex/compare/v1.16.1...v1.17.0) (2026-07-20)
+71 -1
View File
@@ -46,7 +46,77 @@ Detailed architecture documentation is in `docs/CODEMAPS/`:
- **Auto inline review** after each edit: `src/app/subagent/auto.rs``spawn_quick_review()` injects verdict back into LLM conversation. - **Auto inline review** after each edit: `src/app/subagent/auto.rs``spawn_quick_review()` injects verdict back into LLM conversation.
- **Background subagents** (test-gen, arch-review, security-review) fire asynchronously at turn end via `TurnEvent::SystemNote`, retrying once on failure and escalating to a blocking (`ESCALATED:`-prefixed, `ToastKind::Error`) notice if the retry also fails. - **Background subagents** (test-gen, arch-review, security-review) fire asynchronously at turn end via `TurnEvent::SystemNote`, retrying once on failure and escalating to a blocking (`ESCALATED:`-prefixed, `ToastKind::Error`) notice if the retry also fails.
Commit convention (Conventional Commits, Bahasa Indonesia): see the `commit-convention` skill. ---
## Best Practices (Kana Engineering Standards)
This project follows Kana Engineering Best Practices. The following skills are loaded and enforced:
| Skill | Location | Purpose |
|-------|----------|---------|
| `clean-code` | `.claude/skills/clean-code/SKILL.md` | Clean Code principles (naming, functions, classes, comments) |
| `commit-convention` | `.claude/skills/commit-convention/SKILL.md` | Conventional Commits (Bahasa Indonesia) |
| `push-flow-convention` | `.claude/skills/push-flow-convention/SKILL.md` | Pre-commit/pre-push hooks via lefthook |
| `kana-rust-backend-best-practice` | `.claude/skills/kana-rust-backend-best-practice/SKILL.md` | Rust clean-architecture patterns (Axum, SeaORM, etc.) |
### Layering Rules
```
domain/ → application/ → infrastructure/ → interfaces/ → gateway/
(inward) (outward)
```
- **Domain** (Layer 0): Pure entities, value objects, repository/service traits. ZERO external framework deps.
- **Application** (Layer 1): Use-case services (one per file), port traits. Depends ONLY on domain.
- **Infrastructure** (Layer 2): Concrete implementations of domain traits (SQLite, JSON files, LLM clients, LSP servers, MCP).
- **Interfaces** (Layer 3): Presentation adapters — TUI (ratatui), API (Axum), WebSocket, daemon, gRPC, web.
- **Gateway**: Composition root — the only place that wires all layers together.
**Critical:** Domain must NEVER import application, infrastructure, or interfaces. Application must NEVER import infrastructure or interfaces.
### Commit Convention (Bahasa Indonesia)
All commits follow Conventional Commits in Bahasa Indonesia:
```
feat(tool): add batch file delete
fix(ipc): reconnect loop on socket timeout
chore: bump reqwest to 0.13
docs: add architecture diagram to README
refactor(harness): flatten guard pipeline
```
Types: `feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `style`, `perf`, `ci`. All types produce a release (patch minimum). Add `BREAKING CHANGE:` for major bumps.
### Clean Code Principles
- **Functions under ~40 lines**, one level of abstraction, extracted till you drop.
- **No flag arguments** — split `render(true)` into `renderForSuite()` / `renderForSingleTest()`.
- **Command-Query Separation** — function either does or answers, never both.
- **No switch/if-else on type** — replace with factory + polymorphism.
- **No null returns** — use `Option<T>` or empty collections.
- **No magic numbers** — extract named constants.
- **DRY** — no duplication.
- **Tell, Don't Ask** — don't fetch state then decide; tell the object to work.
- **Boy Scout Rule** — leave every module cleaner than you found it.
### Error Handling
- `anyhow::Result` and `anyhow::bail!` throughout (except domain layer typed errors).
- `tracing::warn!` / `tracing::error!` for logging. NEVER stderr (corrupts TUI).
- Never `.unwrap()` or `.expect()` in production code — use `?` or proper error handling.
- Log expected failures at `warn!`, unexpected errors at `error!`.
### Testing
- `#[cfg(test)] mod tests` blocks inline in production files.
- Tests are F.I.R.S.T. — Fast, Independent, Repeatable, Self-validating, Timely.
- Use `Result<()>` as test return type for `?` propagation.
- Mock at boundaries only; prefer fakes for owned abstractions.
### Compiler Bypasses
NEVER use `#[allow(...)]`, `#[expect(...)]`, or `#[allow(dead_code)]`. Fix the underlying code instead.
## Code Documentation ## Code Documentation
Generated
+11 -11
View File
@@ -4895,7 +4895,7 @@ dependencies = [
[[package]] [[package]]
name = "zesdex-api" name = "zesdex-api"
version = "1.17.0" version = "1.18.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"argon2", "argon2",
@@ -4918,7 +4918,7 @@ dependencies = [
[[package]] [[package]]
name = "zesdex-application" name = "zesdex-application"
version = "1.17.0" version = "1.18.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
@@ -4935,7 +4935,7 @@ dependencies = [
[[package]] [[package]]
name = "zesdex-bootstrap" name = "zesdex-bootstrap"
version = "1.17.0" version = "1.18.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@@ -4952,7 +4952,7 @@ dependencies = [
[[package]] [[package]]
name = "zesdex-daemon" name = "zesdex-daemon"
version = "1.17.0" version = "1.18.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
@@ -4976,7 +4976,7 @@ dependencies = [
[[package]] [[package]]
name = "zesdex-domain" name = "zesdex-domain"
version = "1.17.0" version = "1.18.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
@@ -4992,7 +4992,7 @@ dependencies = [
[[package]] [[package]]
name = "zesdex-gateway" name = "zesdex-gateway"
version = "1.17.0" version = "1.18.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"axum", "axum",
@@ -5019,7 +5019,7 @@ dependencies = [
[[package]] [[package]]
name = "zesdex-grpc" name = "zesdex-grpc"
version = "1.17.0" version = "1.18.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"axum", "axum",
@@ -5036,7 +5036,7 @@ dependencies = [
[[package]] [[package]]
name = "zesdex-infrastructure" name = "zesdex-infrastructure"
version = "1.17.0" version = "1.18.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"argon2", "argon2",
@@ -5085,7 +5085,7 @@ dependencies = [
[[package]] [[package]]
name = "zesdex-tui" name = "zesdex-tui"
version = "1.17.0" version = "1.18.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
@@ -5111,7 +5111,7 @@ dependencies = [
[[package]] [[package]]
name = "zesdex-web" name = "zesdex-web"
version = "1.17.0" version = "1.18.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"axum", "axum",
@@ -5131,7 +5131,7 @@ dependencies = [
[[package]] [[package]]
name = "zesdex-ws" name = "zesdex-ws"
version = "1.17.0" version = "1.18.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"axum", "axum",
+1 -1
View File
@@ -15,7 +15,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "1.17.0" version = "1.18.0"
edition = "2021" edition = "2021"
authors = ["asepharyana <superaseph@gmail.com>"] authors = ["asepharyana <superaseph@gmail.com>"]
+57
View File
@@ -0,0 +1,57 @@
//! Mandatory explore phase — spawns parallel subagents to discover context
//! before the main agent begins its turn.
//!
//! # Flow
//!
//! Before the main agent's LLM loop, [`ExploreService::explore`] dispatches
//! at least 3 subagents in parallel (code-structure scan, symbol-index query,
//! semantic-context search). Their findings are consolidated into a single
//! system message that is prepended to the conversation.
//!
//! # Why mandatory
//!
//! Without structured exploration the main agent works from an empty context
//! window. The explore phase guarantees that every turn starts with a compact
//! snapshot of what the codebase contains and where relevant code lives.
use anyhow::Result;
use std::collections::VecDeque;
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use zesdex_domain::agent::TurnEvent;
/// The consolidated output of an explore phase — a set of system-level
/// context messages injected before the main agent prompt.
#[derive(Debug, Clone)]
pub struct ExploreOutput {
/// One or more system messages summarising what the explore subagents
/// discovered. Prepended to the conversation by the turn service.
pub context_messages: Vec<String>,
/// Short human-readable summary of what was explored.
pub summary: String,
}
/// Service trait for the mandatory pre-turn exploration phase.
///
/// Implementors spawn ≥3 parallel subagents, each analysing a different
/// aspect of the workspace, and return a consolidated summary.
///
/// # Object safety
///
/// This trait is `dyn`-safe — it returns `Pin<Box<dyn Future>>` so it can
/// be stored as `Arc<dyn ExploreService>`.
pub trait ExploreService: Send + Sync {
/// Run the explore phase.
///
/// `query` — the user's current input phrase.
/// `workspace_root` — absolute path to the workspace root.
/// `turn_events` — shared event queue for TUI updates.
/// Returns structured context messages and a summary blob.
fn explore<'a>(
&'a self,
query: &'a str,
workspace_root: &'a str,
turn_events: &'a Arc<Mutex<VecDeque<TurnEvent>>>,
) -> Pin<Box<dyn Future<Output = Result<ExploreOutput>> + Send + 'a>>;
}
+29
View File
@@ -0,0 +1,29 @@
use anyhow::Result;
use std::future::Future;
use zesdex_domain::agent::AgentTurnParams;
/// Interface for dispatching tool calls to their concrete implementations.
pub trait ToolExecutor: Send + Sync {
/// Execute a tool call asynchronously.
fn execute(
&self,
tool_name: &str,
args: &serde_json::Value,
) -> impl Future<Output = Result<String>> + Send;
}
/// Service for running agent turns asynchronously.
pub trait AgentTurnService: Send + Sync {
/// Run a full agent turn loop asynchronously.
fn run_turn(
&self,
params: AgentTurnParams,
) -> impl Future<Output = Result<()>> + Send;
}
pub mod explore;
pub mod turn_service;
pub use explore::{ExploreOutput, ExploreService};
pub use turn_service::{compact_messages_with_ai, AgentTurnServiceImpl};
+377
View File
@@ -0,0 +1,377 @@
use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use tracing::{debug, info, warn};
use zesdex_domain::agent::{AgentTurnParams, TurnEvent};
use zesdex_domain::core::{ChatMessage, StreamEvent, ToolDef};
use zesdex_domain::main_agent_prompt;
use crate::ports::ProviderService;
use super::{ExploreService, ToolExecutor};
/// Maximum tool-call iterations per agent turn before forcing termination.
const MAX_TURN_ITERATIONS: u32 = 50;
// ---------------------------------------------------------------------------
// Helper: push a TurnEvent onto the shared queue.
// ---------------------------------------------------------------------------
fn push_event(queue: &Arc<Mutex<VecDeque<TurnEvent>>>, event: TurnEvent) {
if let Ok(mut q) = queue.lock() {
q.push_back(event);
}
}
// ---------------------------------------------------------------------------
// Helper: stream-event callback that forwards tokens to the turn-event queue
// and checks the abort flag on each emission.
// ---------------------------------------------------------------------------
fn make_stream_callback(
abort: &Arc<AtomicBool>,
turn_events: &Arc<Mutex<VecDeque<TurnEvent>>>,
) -> Box<dyn FnMut(&StreamEvent) -> bool + Send> {
let abort_clone = Arc::clone(abort);
let events_clone = Arc::clone(turn_events);
Box::new(move |event: &StreamEvent| -> bool {
if abort_clone.load(Ordering::SeqCst) {
return false;
}
match event {
StreamEvent::Token(s) => {
push_event(&events_clone, TurnEvent::StreamToken(s.clone()));
}
StreamEvent::Reasoning(s) => {
push_event(&events_clone, TurnEvent::StreamReasoning(s.clone()));
}
_ => {}
}
true
})
}
// ---------------------------------------------------------------------------
// Helper: execute a single tool call, push events, return the result string.
// ---------------------------------------------------------------------------
async fn execute_tool_call<T: ToolExecutor>(
tool_executor: &T,
turn_events: &Arc<Mutex<VecDeque<TurnEvent>>>,
tc: &zesdex_domain::core::ToolCall,
) -> String {
let name = &tc.function.name;
let args = zesdex_domain::core::tool_call::sanitize_tool_arguments(&tc.function.arguments);
debug!("executing tool: {name}");
let output = match tool_executor.execute(name, &args).await {
Ok(o) => o,
Err(e) => format!("Error: {e}"),
};
let is_error = output.starts_with("Error:");
push_event(
turn_events,
TurnEvent::ToolResult {
tool_call_id: tc.id.clone(),
tool_name: name.clone(),
output: output.clone(),
is_error,
path: None,
},
);
output
}
// ---------------------------------------------------------------------------
// Helper: emit usage event from optional LLM response metadata.
// ---------------------------------------------------------------------------
fn emit_usage(turn_events: &Arc<Mutex<VecDeque<TurnEvent>>>, usage: Option<(u64, u64)>) {
if let Some((tokens_in, tokens_out)) = usage {
push_event(
turn_events,
TurnEvent::Usage {
tokens_in,
tokens_out,
},
);
}
}
// ---------------------------------------------------------------------------
// Service implementation
// ---------------------------------------------------------------------------
/// Service implementation for executing an agent turn asynchronously.
///
/// # Explore phase
///
/// Before the main LLM loop begins, [`AgentTurnServiceImpl`] runs a mandatory
/// explore phase that spawns ≥3 parallel subagents (code structure, symbol
/// index, semantic context) and injects their consolidated findings as a
/// system message. See [`ExploreService`] for the trait contract.
pub struct AgentTurnServiceImpl<P: ProviderService, T: ToolExecutor> {
provider: Arc<P>,
tool_executor: Arc<T>,
tool_defs: Vec<ToolDef>,
/// Optional explore-phase service. When `Some`, the explore phase runs
/// before every turn; when `None` it is skipped (tests, daemon mode).
explore_service: Option<Arc<dyn ExploreService>>,
}
impl<P: ProviderService, T: ToolExecutor> AgentTurnServiceImpl<P, T> {
pub fn new(
provider: Arc<P>,
tool_executor: Arc<T>,
tool_defs: Vec<ToolDef>,
) -> Self {
Self {
provider,
tool_executor,
tool_defs,
explore_service: None,
}
}
/// Attach an optional explore-phase service.
///
/// When set, every call to `run_turn` will first run the explore phase
/// and inject the consolidated context as a system message.
pub fn with_explore(mut self, service: Arc<dyn ExploreService>) -> Self {
self.explore_service = Some(service);
self
}
/// Execute a single LLM call with the current message list, handling
/// streaming events and error reporting.
async fn call_llm(
&self,
messages: &[ChatMessage],
abort: &Arc<AtomicBool>,
turn_events: &Arc<Mutex<VecDeque<TurnEvent>>>,
) -> Result<(ChatMessage, Option<(u64, u64)>), String> {
let on_event = make_stream_callback(abort, turn_events);
self.provider
.chat_stream(
messages,
Some(self.tool_defs.clone()),
Some(4096),
Some(0.7),
on_event,
)
.await
.map_err(|e| format!("LLM error: {e}"))
}
}
impl<P: ProviderService, T: ToolExecutor> super::AgentTurnService for AgentTurnServiceImpl<P, T> {
async fn run_turn(&self, mut params: AgentTurnParams) -> anyhow::Result<()> {
info!(
"Starting async agent turn with {} messages (model: {})",
params.messages.len(),
params.model
);
// ── Phase 0: Mandatory explore ──────────────────────────────────
// Spawn ≥3 parallel subagents to discover code structure, symbols,
// and semantic context. The consolidated summary is injected as a
// system message before the main agent prompt.
if let Some(ref explorer) = self.explore_service {
// Determine workspace root from the first message's context or
// the first workspace root in params.
let user_query = params
.messages
.last()
.map(|m| m.content.clone().unwrap_or_default())
.unwrap_or_default();
let workspace_root = params
.workspace_roots
.first()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|| ".".to_string());
push_event(
&params.turn_events,
TurnEvent::SystemNote {
kind: "info".into(),
message: "🔍 Exploring codebase structure...".into(),
},
);
match explorer.explore(&user_query, &workspace_root, &params.turn_events).await {
Ok(output) => {
// Insert each context message as a system message.
// They go at index 0 and are removed after the turn
// like the main agent prompt.
for ctx_msg in &output.context_messages {
params
.messages
.insert(0, ChatMessage::system(ctx_msg.clone()));
}
info!(
"Explore phase complete: {} context messages, {}",
output.context_messages.len(),
output.summary
);
}
Err(e) => {
warn!("Explore phase failed (non-fatal): {e}");
push_event(
&params.turn_events,
TurnEvent::SystemNote {
kind: "warn".into(),
message: format!("Explore phase failed: {e}"),
},
);
}
}
}
// Insert system prompt at position 0 once and keep it there for the
// entire turn, avoiding per-iteration clones of the full message list.
// It is removed before emitting the Compacted event so persistence
// does not store the prompt redundantly.
params.messages.insert(0, ChatMessage::system(main_agent_prompt()));
let original_count = params.messages.len();
for iteration in 0..MAX_TURN_ITERATIONS {
// ── Check abort flag ────────────────────────────────────────
if params.abort.load(Ordering::SeqCst) {
params.abort.store(false, Ordering::SeqCst);
push_event(
&params.turn_events,
TurnEvent::SystemNote {
kind: "info".into(),
message: "Turn aborted by user".into(),
},
);
break;
}
debug!("agent turn iteration {iteration}");
// ── Stream start + call LLM ─────────────────────────────────
push_event(&params.turn_events, TurnEvent::StreamStart);
// Uses params.messages directly (sys_msg[0] already in place
// from the insert above) — no per-iteration clone needed.
let result = self
.call_llm(&params.messages, &params.abort, &params.turn_events)
.await;
match result {
Ok((assistant_msg, usage)) => {
let content = assistant_msg.content.clone().unwrap_or_default();
let tool_calls = assistant_msg.tool_calls.clone().unwrap_or_default();
push_event(
&params.turn_events,
TurnEvent::StreamDone(assistant_msg.clone()),
);
emit_usage(&params.turn_events, usage);
// ── No tool calls → assistant is done ──────────────
if tool_calls.is_empty() {
params.messages.push(ChatMessage::assistant(Some(content)));
break;
}
params.messages.push(assistant_msg);
// ── Execute each tool call ──────────────────────────
for tc in &tool_calls {
let output =
execute_tool_call(self.tool_executor.as_ref(), &params.turn_events, tc).await;
params
.messages
.push(ChatMessage::tool(tc.id.clone(), output));
}
}
Err(e) => {
warn!("{e}");
push_event(
&params.turn_events,
TurnEvent::Error(e),
);
break;
}
}
}
// Remove the synthetic sys_msg before shipping events to the TUI
// so the transcript shows only the actual user/assistant/tool exchange.
let compacted: Vec<ChatMessage> = params.messages.drain(original_count - 1..).collect();
push_event(
&params.turn_events,
TurnEvent::Compacted(compacted),
);
push_event(&params.turn_events, TurnEvent::Done);
params.in_flight.store(false, Ordering::SeqCst);
Ok(())
}
}
// ---------------------------------------------------------------------------
// Conversation compaction
// ---------------------------------------------------------------------------
/// Maximum number of recent messages to preserve during compaction.
const COMPACT_KEEP_TAIL: usize = 6;
/// Compacts conversation history using AI summarisation.
///
/// Flow: if the message count exceeds `KEEP_TAIL + 2`, the oldest messages
/// are drained and summarised by the LLM. The summary is inserted as a
/// system message at the head of the remaining history.
pub async fn compact_messages_with_ai<P: ProviderService>(
messages: &mut Vec<ChatMessage>,
provider: &P,
) -> anyhow::Result<()> {
if messages.len() <= COMPACT_KEEP_TAIL + 2 {
return Ok(()); // Not enough messages to compact
}
let split_idx = messages.len() - COMPACT_KEEP_TAIL;
let evicted: Vec<_> = messages.drain(..split_idx).collect();
let mut summary_prompt = vec![
ChatMessage::system(zesdex_domain::compaction_prompt()),
];
summary_prompt.extend(evicted);
summary_prompt.push(ChatMessage::user(
"Please summarise our previous conversation above for context continuity.".to_string(),
));
match provider.chat(&summary_prompt, None, Some(1024), Some(0.3)).await {
Ok((summary_msg, _)) => {
let summary_text = summary_msg
.content
.unwrap_or_else(|| "Previous context summarised.".to_string());
let summary_node = ChatMessage::system(format!(
"[AI Summary of Previous Conversation]\n{}",
summary_text.trim()
));
messages.insert(0, summary_node);
Ok(())
}
Err(e) => {
warn!("AI summarisation failed during compact, falling back to simple notice: {e}");
messages.insert(
0,
ChatMessage::system(
"[Earlier conversation messages compacted to save context window]".to_string(),
),
);
Ok(())
}
}
}
+6
View File
@@ -33,6 +33,7 @@
pub mod auth; pub mod auth;
pub mod cms; pub mod cms;
pub mod ports; pub mod ports;
pub mod agent;
// Re-export port traits for ergonomic access. // Re-export port traits for ergonomic access.
pub use ports::*; pub use ports::*;
@@ -49,3 +50,8 @@ pub use cms::{
memory_service::MemoryServiceImpl, memory_service::MemoryServiceImpl,
settings_service::SettingsServiceImpl, settings_service::SettingsServiceImpl,
}; };
pub use agent::{
AgentTurnService, ExploreOutput, ExploreService, ToolExecutor,
turn_service::{AgentTurnServiceImpl, compact_messages_with_ai},
};
+34
View File
@@ -0,0 +1,34 @@
//! Shared default constants used across the application.
//!
//! Centralising these values eliminates the hardcoded-string duplication
//! that existed when every call site provided its own inline fallback.
//! Consumers should reference these constants rather than repeating
//! the string literals.
/// Default LLM provider API base URL.
pub const DEFAULT_API_BASE: &str = "https://opencode.ai/zen/v1";
/// Default LLM model identifier.
pub const DEFAULT_MODEL: &str = "deepseek-v4-flash-free";
/// Fallback JWT secret used only when `JWT_SECRET` env var is unset.
/// In production this MUST be configured via environment variable.
pub const FALLBACK_JWT_SECRET: &str = "dev-secret";
/// Default context window size (128k tokens).
pub const DEFAULT_CONTEXT_WINDOW: usize = 256_000;
/// Maximum tool-call iterations per agent turn.
pub const MAX_TOOL_ITERATIONS: u32 = 50;
/// Maximum subagent tool-call iterations.
pub const MAX_SUBAGENT_ITERATIONS: u32 = 25;
/// Default LLM request max tokens.
pub const DEFAULT_MAX_TOKENS: u32 = 4096;
/// Default temperature for the main agent.
pub const DEFAULT_TEMPERATURE: f64 = 0.7;
/// Default temperature for compaction / summary calls.
pub const DEFAULT_COMPACT_TEMPERATURE: f64 = 0.3;
+260
View File
@@ -0,0 +1,260 @@
//! Domain types for agent lifecycle: turn events, session runtime, progress
//! reporting, prompts, and the agent-turn parameter bundle.
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::core::{ChatMessage, ToolCallResult, UsageStats};
pub mod defaults;
pub mod prompt;
pub mod progress;
/// Which kind of caller (main agent vs. subagent vs. reviewer) is
/// invoking a tool, used to scope permissions and tag log/output paths.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub enum Origin {
/// The main agent turn loop.
Main,
/// A spawned subagent (test-gen, arch-review, security-review, etc.).
SubAgent,
/// The auto-inline review step after an edit.
Reviewer,
}
impl Origin {
/// Short string tag for this origin, used in filenames and logs.
pub fn tag(self) -> String {
match self {
Origin::Main => "main",
Origin::SubAgent => "subagent",
Origin::Reviewer => "reviewer",
}
.to_string()
}
}
/// Severity/category of a toast notification, used to pick its color.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ToastKind {
Info,
Success,
Warning,
Error,
Lesson,
}
/// A transient status message shown in the TUI, auto-dismissed after `lifetime_ms`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Toast {
pub kind: ToastKind,
pub message: String,
pub created_at: i64,
pub lifetime_ms: u64,
}
impl Toast {
/// Create a toast with a default 5-second lifetime, stamped with now.
pub fn new(kind: ToastKind, message: String) -> Self {
Toast {
kind,
message,
created_at: chrono::Utc::now().timestamp_millis(),
lifetime_ms: 5000,
}
}
/// Whether this toast's lifetime has elapsed as of `now_ms`.
pub fn expired(&self, now_ms: i64) -> bool {
let lifetime = self.lifetime_ms as i64;
now_ms - self.created_at > lifetime
}
}
/// Agent status for workflow engine progress tracking.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum AgentStatus {
Pending,
Running,
Completed,
Failed(String),
Cancelled,
}
impl std::fmt::Display for AgentStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AgentStatus::Pending => write!(f, "pending"),
AgentStatus::Running => write!(f, "running"),
AgentStatus::Completed => write!(f, "completed"),
AgentStatus::Failed(msg) => write!(f, "failed: {msg}"),
AgentStatus::Cancelled => write!(f, "cancelled"),
}
}
}
/// Events emitted onto the turn-event queue while an agent turn runs,
/// consumed by the event loop to update state and drive re-renders.
#[derive(Debug, Clone)]
pub enum TurnEvent {
AssistantMessage(ChatMessage),
ToolResult {
tool_call_id: String,
tool_name: String,
output: String,
is_error: bool,
path: Option<String>,
},
SystemNote {
kind: String,
message: String,
},
StreamStart,
StreamToken(String),
StreamReasoning(String),
StreamDone(ChatMessage),
Usage {
tokens_in: u64,
tokens_out: u64,
},
ReviewUsage {
tokens_in: u64,
tokens_out: u64,
},
Compacted(Vec<ChatMessage>),
Error(String),
Done,
WorkflowAgentUpdate {
agent_id: String,
agent_name: String,
status: AgentStatus,
},
TodoUpdate(String),
PlanUpdate(String),
/// Structured progress report from a subagent or workflow node,
/// carrying the current tool name and optional step counters.
AgentProgress(crate::agent::progress::AgentProgress),
}
/// How a pending tool call should be executed when the turn resumes.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ExecutionModel {
Inline,
Deferred,
AsyncTokio,
}
/// A tool call awaiting execution, along with which execution model
/// (inline, deferred, async) it should run under.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PendingTool {
pub tool_name: String,
pub args: serde_json::Value,
pub execution_model: ExecutionModel,
}
/// Reference to a background bash job tracked in session state.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BashJobRef {
pub id: String,
pub command: String,
pub started_at: i64,
pub running: bool,
}
/// Tracks counts of learned patterns by outcome and lifecycle stage.
#[derive(Debug, Clone, Default)]
pub struct LessonStats {
/// Total number of lessons tracked.
pub total: u32,
/// User-initiated lessons.
pub user: u32,
/// Feedback-driven lessons.
pub feedback: u32,
/// Project-scoped lessons.
pub project: u32,
/// Reference-scoped lessons.
pub reference: u32,
/// Currently active lessons.
pub active: u32,
/// Stale (outdated) lessons.
pub stale: u32,
/// Contradicted lessons.
pub contradicted: u32,
/// Human-authored lessons.
pub human: u32,
/// Verified lessons.
pub verified: u32,
/// Unverified lessons.
pub unverified: u32,
}
/// Per-session runtime state: message history, pending tool queue,
/// background bash jobs, lesson/review counters.
#[derive(Debug, Clone)]
pub struct SessionRuntime {
pub messages: Vec<ChatMessage>,
pub tool_call_results: Vec<ToolCallResult>,
pub pending_tool_queue: Vec<PendingTool>,
pub bash_jobs: Vec<BashJobRef>,
pub subagent_queue: usize,
pub edit_count: u32,
pub consecutive_empty_reviews: u32,
pub session_start: i64,
/// Aggregated lesson statistics.
pub lessons: LessonStats,
pub review_count: u32,
pub session_dir: PathBuf,
pub usage: UsageStats,
pub hive_mind_converged: bool,
}
impl SessionRuntime {
pub fn new(session_dir: PathBuf) -> Self {
SessionRuntime {
messages: Vec::new(),
tool_call_results: Vec::new(),
pending_tool_queue: Vec::new(),
bash_jobs: Vec::new(),
subagent_queue: 0,
edit_count: 0,
consecutive_empty_reviews: 0,
session_start: chrono::Utc::now().timestamp_millis(),
lessons: LessonStats::default(),
review_count: 0,
session_dir,
usage: UsageStats::default(),
hive_mind_converged: false,
}
}
pub fn push_message(&mut self, msg: ChatMessage) {
self.messages.push(msg);
}
}
/// Simple ASCII progress display for a long-running operation.
#[derive(Debug, Clone)]
pub struct ProgressState {
pub current: u64,
pub total: u64,
pub message: String,
pub start_time: i64,
}
use std::collections::VecDeque;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Mutex};
/// Owned parameters required to spawn and execute an agent turn.
pub struct AgentTurnParams {
pub messages: Vec<ChatMessage>,
pub session_dir: PathBuf,
pub workspace_roots: Vec<PathBuf>,
pub turn_events: Arc<Mutex<VecDeque<TurnEvent>>>,
pub in_flight: Arc<AtomicBool>,
pub abort: Arc<AtomicBool>,
pub api_key: String,
pub model: String,
pub api_base: Option<String>,
}
+81
View File
@@ -0,0 +1,81 @@
//! Progress reporting types for long-running agent and subagent operations.
//!
//! These types are emitted onto the turn-event queue to drive the TUI's
//! spinner, progress bar, and agent-status sidebar. They are pure domain
//! types with no I/O or framework dependency.
use crate::agent::AgentStatus;
/// Describes progress within a single subagent or workflow-node execution.
///
/// Emitted as a `TurnEvent::AgentProgress` so the UI can show which tool
/// the subagent is currently invoking, or which step it has reached.
#[derive(Debug, Clone)]
pub struct AgentProgress {
/// Unique identifier for this agent (e.g. `"Node-0-1"`, `"auto-review"`).
pub agent_id: String,
/// Human-readable display name shown in the TUI sidebar.
pub agent_name: String,
/// Current lifecycle status.
pub status: AgentStatus,
/// Optional description of the current tool or step being executed.
/// Set to `None` when the agent is not actively executing a tool.
pub current_tool: Option<String>,
/// Optional progress range: (completed_steps, total_steps).
/// When `None`, the agent shows an indeterminate spinner.
pub steps: Option<(u32, u32)>,
}
impl AgentProgress {
/// Mark this agent as running with an optional tool name.
pub fn running(
agent_id: impl Into<String>,
agent_name: impl Into<String>,
current_tool: Option<String>,
) -> Self {
AgentProgress {
agent_id: agent_id.into(),
agent_name: agent_name.into(),
status: AgentStatus::Running,
current_tool,
steps: None,
}
}
/// Mark this agent as pending (queued but not yet started).
pub fn pending(agent_id: impl Into<String>, agent_name: impl Into<String>) -> Self {
AgentProgress {
agent_id: agent_id.into(),
agent_name: agent_name.into(),
status: AgentStatus::Pending,
current_tool: None,
steps: None,
}
}
/// Mark this agent as completed successfully.
pub fn completed(agent_id: impl Into<String>, agent_name: impl Into<String>) -> Self {
AgentProgress {
agent_id: agent_id.into(),
agent_name: agent_name.into(),
status: AgentStatus::Completed,
current_tool: None,
steps: None,
}
}
/// Mark this agent as failed with an error message.
pub fn failed(
agent_id: impl Into<String>,
agent_name: impl Into<String>,
error: String,
) -> Self {
AgentProgress {
agent_id: agent_id.into(),
agent_name: agent_name.into(),
status: AgentStatus::Failed(error),
current_tool: None,
steps: None,
}
}
}
+92
View File
@@ -0,0 +1,92 @@
//! System prompts and directive templates for agent and subagent turns.
//!
//! Centralising all prompt text here keeps the core turn logic free of
//! hardcoded prose, making prompts easier to maintain, review, and localise.
//!
//! # Flow
//! The application layer's `AgentTurnServiceImpl` calls `main_agent_prompt()`
//! to construct the system message at the start of each turn. Subagent and
//! review prompts are provided by their respective modules.
/// Build the main-agent system prompt.
///
/// The prompt establishes the agent's identity as Zesdex, an AI coding
/// assistant, and defines the priority hierarchy that governs tool selection:
///
/// 1. **Workflow first** — `workflow_run` / `hive_mind` for complex tasks
/// 2. **Planning & TODOs** — `plan_enter` / `todowrite` for structural work
/// 3. **Reasoning** — `seq_think` for deep analysis
/// 4. **Tool execution** — direct tools for simple actions
pub fn main_agent_prompt() -> String {
"\
You are Zesdex, an AI coding assistant. You have access to various tools \
via native function calling to help the user.
CRITICAL DIRECTIVES & PRIORITY HIERARCHY:
1. WORKFLOW FIRST: For any multi-step, complex, or non-trivial task, \
you MUST prioritise using `workflow_run` (to construct and execute a \
multi-phase YAML workflow) or `hive_mind` (to orchestrate parallel \
autonomous agents). Workflows are your primary strategy.
2. PLANNING & TODOS: Use `plan_enter` to establish high-level \
architectural plans and `todowrite` to maintain granular task checklists.
3. REASONING: Use `seq_think` for deep step-by-step analysis.
4. TOOL EXECUTION: Execute individual tools (file edits, terminal commands) \
within or guided by your workflows. If an error occurs, analyse and fix it.
Respond conversationally, concisely, and helpfully."
.to_string()
}
/// Build a subagent directive prompt.
///
/// The directive is embedded in a system message that also communicates the
/// current working directory and workspace root so the subagent can resolve
/// paths correctly.
pub fn subagent_directive(directive: &str, cwd: &str, ws_root: &str) -> String {
format!(
"\
You are a focused subagent.
Current directory (PWD): {cwd}
Workspace root: {ws_root}
Your directive:
{directive}
Complete the directive autonomously using the tools available to you. \
Return your final answer when done."
)
}
/// Build a conversation-compaction prompt.
///
/// The LLM is asked to produce a concise bulleted summary of the key
/// requests, decisions, tools executed, and files modified.
pub fn compaction_prompt() -> String {
"\
You are a helpful assistant summarising conversation history. \
Provide a concise summary of the key user requests, decisions, tools \
executed, and modified files. Format as a clear bulleted list."
.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn main_prompt_is_non_empty() {
let prompt = main_agent_prompt();
assert!(!prompt.is_empty());
assert!(prompt.contains("Zesdex"));
assert!(prompt.contains("WORKFLOW FIRST"));
}
#[test]
fn subagent_directive_includes_directive_text() {
let prompt = subagent_directive("test directive", "/home", "/home/project");
assert!(prompt.contains("test directive"));
assert!(prompt.contains("/home"));
assert!(prompt.contains("/home/project"));
}
}
+34 -18
View File
@@ -104,36 +104,52 @@ impl SessionLock {
let _ = fs::remove_file(&self.path); let _ = fs::remove_file(&self.path);
} }
/// Check whether a process with the given PID is currently alive. /// Check whether a process with the given PID is currently alive and
/// belongs to the same binary (mitigating PID-reuse races).
///
/// Strategy (Unix):
/// 1. Resolve `/proc/<pid>/exe` — if it doesn't match our own binary,
/// the PID either belongs to another process or is reused — return false.
/// 2. Send `kill(pid, 0)` to verify the process is still alive.
/// 3. Re-check `/proc/<pid>/exe` to close the TOCTOU window between
/// step 1 and step 2 (PID reuse after exe check, before kill).
/// ///
/// Uses `kill(pid, 0)` on Unix via the `nix` or `libc` crate in production;
/// here we provide a best-effort check using the process table.
/// On non-Unix platforms this always returns `true` (conservative). /// On non-Unix platforms this always returns `true` (conservative).
fn is_alive(pid: u32) -> bool { fn is_alive(pid: u32) -> bool {
// On Unix, signal 0 checks process existence without sending a signal.
#[cfg(unix)] #[cfg(unix)]
{ {
// SAFETY: `libc::kill(pid, 0)` does not send a signal; it only checks // Resolve our own executable path once.
// whether the process exists and the caller has permission to signal it. let self_exe = match std::fs::read_link("/proc/self/exe") {
// The integer argument is a PID validated by `try_lock`. Ok(exe) => exe,
Err(_) => return false,
};
let pid_signed: i32 = match pid.try_into() { let pid_signed: i32 = match pid.try_into() {
Ok(p) => p, Ok(p) => p,
Err(_) => return false, Err(_) => return false,
}; };
let proc_exe = std::path::PathBuf::from(format!("/proc/{pid}/exe"));
// Phase 1: read /proc/<pid>/exe and compare with self_exe.
let target = match std::fs::read_link(&proc_exe) {
Ok(t) => t,
Err(_) => return false,
};
if target != self_exe {
return false;
}
// Phase 2: verify the process is still alive.
// SAFETY: `libc::kill(pid, 0)` does not send a signal; it only checks
// whether the process exists and the caller has permission to signal it.
if unsafe { libc::kill(pid_signed, 0) != 0 } { if unsafe { libc::kill(pid_signed, 0) != 0 } {
return false; return false;
} }
// Extra check: verify the PID belongs to a zesdex process via
// /proc/<pid>/exe to mitigate the PID-reuse race. // Phase 3: re-check /proc/<pid>/exe to detect PID reuse between
let proc_exe = std::path::PathBuf::from(format!("/proc/{pid}/exe")); // Phase 1 and Phase 2.
if let Ok(target) = std::fs::read_link(&proc_exe) { matches!(std::fs::read_link(&proc_exe), Ok(recheck) if recheck == self_exe)
if let Ok(exe) = std::env::current_exe() {
if target != exe {
return false;
}
}
}
true
} }
#[cfg(not(unix))] #[cfg(not(unix))]
+35 -4
View File
@@ -218,14 +218,27 @@ impl SseParser {
/// ///
/// Return: all `StreamEvent`s completed by this chunk. /// Return: all `StreamEvent`s completed by this chunk.
pub fn feed(&mut self, chunk: &str) -> Vec<StreamEvent> { pub fn feed(&mut self, chunk: &str) -> Vec<StreamEvent> {
self.buffer.push_str(chunk); // Normalize \r\n and bare \r to \n for consistent line ending handling
let chunk = chunk.replace("\r\n", "\n").replace('\r', "\n");
// Prevent unbounded buffer growth for long lines without \n
const MAX_BUFFER_SIZE: usize = 1_048_576; // 1 MB
if self.buffer.len() + chunk.len() > MAX_BUFFER_SIZE {
tracing::warn!("SSE buffer exceeded maximum size, resetting");
self.buffer.clear();
self.event_type = None;
self.data_lines.clear();
}
self.buffer.push_str(&chunk);
let mut events = Vec::new(); let mut events = Vec::new();
while let Some(line_end) = self.buffer.find('\n') { while let Some(line_end) = self.buffer.find('\n') {
let line = self.buffer[..line_end].trim_end_matches('\r').to_string(); let line = self.buffer[..line_end].trim_end_matches('\r').to_string();
self.buffer = self.buffer[line_end + 1..].to_string(); self.buffer = self.buffer[line_end + 1..].to_string();
if line.is_empty() { if line.is_empty() {
events.extend(self.flush_event()); events.extend(self.flush_event());
} else if let Some(ty) = line.strip_prefix("event: ") { } else if let Some(ty) = line.strip_prefix("event:") {
// Handle both "event:foo" and "event: foo"
self.event_type = Some(ty.trim().to_string()); self.event_type = Some(ty.trim().to_string());
} else if let Some(data) = line.strip_prefix("data:") { } else if let Some(data) = line.strip_prefix("data:") {
let data = data.trim_start().to_string(); let data = data.trim_start().to_string();
@@ -320,8 +333,9 @@ impl SseParser {
if let Some(tool_calls) = if let Some(tool_calls) =
d.get("tool_calls").and_then(|tc| tc.as_array()) d.get("tool_calls").and_then(|tc| tc.as_array())
{ {
const MAX_TOOL_CALLS: usize = 64;
for tc in tool_calls { for tc in tool_calls {
let index = let raw_index =
tc.get("index").and_then(Value::as_u64).unwrap_or_else( tc.get("index").and_then(Value::as_u64).unwrap_or_else(
|| { || {
tracing::warn!( tracing::warn!(
@@ -331,7 +345,10 @@ impl SseParser {
0 0
}, },
); );
let index = usize::try_from(index).unwrap_or(0); // Clamp index to prevent out-of-bounds / memory exhaustion
let index = usize::try_from(raw_index)
.unwrap_or(0)
.min(MAX_TOOL_CALLS.saturating_sub(1));
let id = tc let id = tc
.get("id") .get("id")
.and_then(|i| i.as_str()) .and_then(|i| i.as_str())
@@ -372,6 +389,20 @@ impl SseParser {
} }
d_events d_events
} }
"content_block_delta" => {
let mut d_events = Vec::new();
if let Some(delta) = value.get("delta") {
if let Some(content) = delta.get("text").and_then(|c| c.as_str()) {
d_events.push(StreamEvent::Token(content.to_string()));
}
if let Some(reasoning) =
delta.get("reasoning_content").and_then(|r| r.as_str())
{
d_events.push(StreamEvent::Reasoning(reasoning.to_string()));
}
}
d_events
}
_ => vec![], _ => vec![],
}; };
+12
View File
@@ -28,6 +28,9 @@ pub mod auth;
pub mod cms; pub mod cms;
pub mod core; pub mod core;
pub mod error; pub mod error;
pub mod agent;
pub mod workflow;
pub mod subagent;
// Re-export all public items from each module for ergonomic imports. // Re-export all public items from each module for ergonomic imports.
// Consumers can do `use zesdex_domain::*` for common types. // Consumers can do `use zesdex_domain::*` for common types.
@@ -50,3 +53,12 @@ pub use core::{
ToolCallResult, ToolDef, ToolFunction, ToolFunctionDef, UsageStats, ToolCallResult, ToolDef, ToolFunction, ToolFunctionDef, UsageStats,
}; };
pub use error::DomainError; pub use error::DomainError;
// Agent module top-level items (TurnEvent, SessionRuntime, etc.)
pub use agent::*;
// Sub-module items need explicit re-exports
pub use agent::defaults::*;
pub use agent::progress::AgentProgress;
pub use agent::prompt::{compaction_prompt, main_agent_prompt, subagent_directive};
pub use workflow::*;
pub use subagent::*;
+15
View File
@@ -0,0 +1,15 @@
//! Subagent domain models.
/// Access tier for subagent tool permissions.
///
/// Tiers are cumulative: `Write` includes everything in `Read`, and `Full`
/// includes everything in `Write`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AccessTier {
/// Read-only: search, read, glob, utility tools (no mutations).
Read,
/// Read + Write: above plus write, edit, delete, git, memory.
Write,
/// Full: above plus bash, shell, LSP, workflow, plan tools.
Full,
}
+45
View File
@@ -0,0 +1,45 @@
//! Workflow and Hive-mind domain models.
/// A single phase in a parsed workflow script.
#[derive(Debug, Clone)]
pub struct WorkflowPhase {
pub name: String,
pub directive: String,
}
/// A parsed workflow script with named phases.
#[derive(Debug, Clone)]
pub struct WorkflowScript {
pub name: String,
pub phases: Vec<WorkflowPhase>,
}
/// A directive for a single processing node in the hive mind.
#[derive(Debug, Clone)]
pub struct NodeDirective {
pub directive: String,
pub access_tier: String,
}
/// A cognitive cycle plan — ordered list of cycles, each containing
/// parallel node directives.
#[derive(Debug, Clone)]
pub struct CognitiveCyclePlan {
pub cycles: Vec<Vec<NodeDirective>>,
}
/// A single cycle in a cognitive cycle plan — parallel node directives
/// executed together.
#[derive(Debug, Clone)]
pub struct CognitiveCycle {
pub index: u32,
pub directives: Vec<NodeDirective>,
}
/// Output from a single hive-mind processing node after a cycle completes.
#[derive(Debug, Clone)]
pub struct NodeOutput {
pub id: String,
pub directive: String,
pub output: String,
}
+5
View File
@@ -42,3 +42,8 @@ dirs.workspace = true
rusqlite.workspace = true rusqlite.workspace = true
axum.workspace = true axum.workspace = true
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
[[bin]]
name = "test_load"
path = "src/bin/test_load.rs"
+20
View File
@@ -0,0 +1,20 @@
use zesdex_domain::cms::AppConfigRepository;
use zesdex_infrastructure::persistence::cms::app_config_repo::JsonAppConfigRepository;
fn main() {
let base_dir = dirs::home_dir().unwrap().join(".local/share/zesdex");
let repo = JsonAppConfigRepository::new();
let config = repo.load(&base_dir).unwrap();
println!("Providers:");
for (k, v) in &config.providers {
println!(" - {} (default model: {:?})", k, v.default_model);
}
println!("Default provider: {}", config.default_provider);
println!("Default model: {}", config.default_model);
println!("Model roles:");
for (k, v) in &config.model_roles {
println!(" - {}: provider={}, model={}", k, v.provider, v.model);
}
}
+43
View File
@@ -0,0 +1,43 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ClaudeEnv {
#[serde(alias = "ANTHROPIC_BASE_URL")]
anthropic_base_url: Option<String>,
#[serde(alias = "ANTHROPIC_API_KEY")]
anthropic_api_key: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ClaudeSettings {
env: Option<ClaudeEnv>,
#[serde(alias = "customModel", alias = "model")]
custom_model: Option<String>,
}
fn main() {
let path = dirs::home_dir().unwrap().join(".claude").join("settings.json");
println!("Path: {:?}", path);
match std::fs::read_to_string(&path) {
Ok(content) => {
println!("File content length: {}", content.len());
match serde_json::from_str::<ClaudeSettings>(&content) {
Ok(settings) => {
println!("Parsed successfully: {:?}", settings);
if let Some(env) = settings.env {
println!("Base URL: {:?}", env.anthropic_base_url);
println!("API Key: {:?}", env.anthropic_api_key);
} else {
println!("No env block");
}
}
Err(e) => {
println!("Parse error: {}", e);
}
}
}
Err(e) => {
println!("Read error: {}", e);
}
}
}
+13 -3
View File
@@ -137,12 +137,22 @@ fn run_api_server(port: u16) -> anyhow::Result<()> {
rt.block_on(async { rt.block_on(async {
let store = zesdex_domain::core::Store::new(); let store = zesdex_domain::core::Store::new();
store.ensure_dirs()?; store.ensure_dirs()?;
// Load JWT secret from environment variable with a secure default warning
let jwt_secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| {
tracing::warn!(
"JWT_SECRET environment variable not set; using insecure default. \
Set JWT_SECRET to a secure random value in production."
);
zesdex_domain::agent::defaults::FALLBACK_JWT_SECRET.to_string()
});
let state = zesdex_api::ApiState::new( let state = zesdex_api::ApiState::new(
store.base_dir.clone(), store.base_dir.clone(),
"dev-secret", jwt_secret,
"", "",
"deepseek-v4-flash-free", zesdex_domain::agent::defaults::DEFAULT_MODEL,
Some("https://opencode.ai/zen/v1".to_string()), Some(zesdex_domain::agent::defaults::DEFAULT_API_BASE.to_string()),
); );
let app = zesdex_api::build_router(state); let app = zesdex_api::build_router(state);
let addr = std::net::SocketAddr::from(([0, 0, 0, 0], port)); let addr = std::net::SocketAddr::from(([0, 0, 0, 0], port));
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,144 @@
# Clean Architecture
When to load this reference: when structuring a new service or module, drawing boundaries between components, deciding what a microservice should own, untangling framework coupling, reviewing a system for testability and longevity, or choosing a top-level folder structure.
Clean Architecture is Uncle Bob's synthesis of Hexagonal Architecture (Alistair Cockburn), Onion Architecture (Jeffrey Palermo), DCI (Coplien & Reenskaug), and BCE (Ivar Jacobson, *Object-Oriented Software Engineering*, 1992). They differ in detail but agree on one goal: **separation of concerns by layering**, with business rules isolated from delivery mechanisms.
The foundational insight comes from Jacobson: **architectures are structures that support the use cases of the system.** Not frameworks. Not databases. Not UIs. Use cases.
---
## What a Clean Architecture Produces
A system that is:
1. **Independent of frameworks.** Frameworks are tools, not constraints.
2. **Testable.** Business rules tested without UI, DB, web server, or any external element.
3. **Independent of UI.** The UI can be replaced (web → console → CLI → TUI) without touching business rules.
4. **Independent of database.** Swap PostgreSQL for MongoDB, ClickHouse, or in-memory without rewriting domain logic.
5. **Independent of any external agency.** The core business rules know nothing about the outside world.
**The database is a detail.** So is the web. So is the framework. These are the most common sources of architectural rot because developers mistake them for foundations.
> "The database is merely an IO device. It happens to provide some useful tools for sorting, querying, and reporting but those are ancillary to the system architecture." — *A Little Architecture* (2016)
---
## The Dependency Rule
The one rule that makes everything else work:
> **Source code dependencies point only inward, toward higher-level policy.**
- Nothing in an inner layer may name anything from an outer layer — no function, class, variable, or data format.
- Data formats convenient for the outer layer (ORM row struct, JSON DTO) must not leak inward.
- Control flow may cross boundaries in either direction, but *source dependencies* point only inward. The Dependency Inversion Principle (see [solid.md](solid.md)) is the mechanism that makes this possible when control flow runs outward.
When this rule is obeyed, external details — databases, frameworks, UIs — become replaceable plugins.
---
## The Four Concentric Layers
Schematic. You may need more or fewer for a given system, but the Dependency Rule always applies.
### 1. Entities (innermost)
Encapsulate **enterprise-wide** business rules. An entity can be a class with methods or a data structure plus functions — style choice.
- Entities know nothing about applications, use cases, frameworks, or anything outside.
- For single applications (no "enterprise"), these are your core business objects.
- These are the least affected by operational change. Changes to page navigation, auth mechanisms, or DB schemas must not reach here.
### 2. Use Cases
Encapsulate **application-specific** business rules. Use cases orchestrate entities to accomplish the application's goals.
- A use case directs entities; it does not contain enterprise-wide rules itself.
- Changes to the application's *behavior* land here. Changes to externalities do not.
- Simple request/response data structures (not entities) flow in and out.
### 3. Interface Adapters
Convert data between the format convenient for use cases/entities and the format convenient for external agencies.
- MVC's Controllers, Presenters, and Views live here.
- All SQL lives here (if the database is SQL). Nothing inside knows about SQL.
- DTOs are translated into domain types and back here.
### 4. Frameworks and Drivers (outermost)
The web framework, the database, the message broker, the file system. Glue code only — you do not write much application logic here. Details live here because details change, and the outer ring is where change is cheap.
---
## Crossing Boundaries
When control flow needs to run outward — a use case needs to call a presenter — a direct call violates the Dependency Rule (the inner layer names something in the outer layer).
**Solution: the Dependency Inversion Principle.** The use case calls an interface (an "output port") defined in its own layer. The outer-layer presenter implements that interface. Control flows outward; source dependencies point inward. Same pattern works for repositories, gateways, any outward call.
---
## What Crosses Boundaries
Only **simple data structures** cross boundaries:
- Plain structs or Data Transfer Objects.
- Primitive arguments in function calls.
- Maps/dictionaries, when appropriate.
Never pass Entity objects or ORM row objects across boundaries — that couples layers. Translate to the format most convenient for the inner circle at every boundary crossing.
---
## Screaming Architecture
From the 2011 blog post of the same name. The top-level layout of a project should *scream* what the system does, not what framework it uses.
**The blueprint metaphor.** Imagine looking at the blueprints of a building. A single-family residence: front entrance, foyer, living room, dining room, kitchen. A library: grand entrance, check-in clerks, reading areas, galleries of bookshelves. A shopping mall: corridors, store bays, parking lots. You can tell what kind of building it is before you see any sign.
What does *your* application architecture scream?
**Bad top-level:** `controllers/`, `models/`, `views/`, `services/`. Tells you the system uses MVC. Tells you nothing about what the system is for.
**Good top-level:** `billing/`, `shipping/`, `catalog/`, `fraud_detection/`. Now you know what the system does.
**Why it matters:** A good architecture lets you defer decisions about Rails, Spring, Hibernate, Tomcat, MySQL, or React until much later in the project. A framework-centric top-level locks those decisions in day one, and also makes the code base mute about its own purpose. The web is a *delivery mechanism*; the database is a *detail*. Neither should dominate your system structure.
If a stranger cannot tell from the directory structure whether they are looking at an e-commerce platform or a hospital records system, the architecture is failing at the highest level.
---
## Component Principles
Once modules are organized, they group into **components** — independently deployable units (libraries, services, jars, crates). Two sets of principles govern them.
### Component Cohesion
- **REP — Reuse/Release Equivalence Principle.** The unit of reuse is the unit of release.
- **CCP — Common Closure Principle.** Group together classes that change for the same reasons at the same times. (SRP at component scale.)
- **CRP — Common Reuse Principle.** Classes used together belong together; classes not used together don't. (ISP at component scale.)
These three pull in different directions — the **tension diagram** is a triangle and component design is an ongoing balance. Early-stage projects lean toward REP+CCP (ship quickly, include more); mature, widely-reused components shift toward CRP (exclude what clients don't need).
### Component Coupling
- **ADP — Acyclic Dependencies Principle.** The dependency graph among components must have no cycles. Break cycles with DIP or by extracting a new component both sides depend on.
- **SDP — Stable Dependencies Principle.** Depend in the direction of stability.
- **SAP — Stable Abstractions Principle.** Stable components should be abstract; volatile components should be concrete.
---
## Applying This in Practice
- **"NO DB" and "NO Web" are valid starting positions.** Business rules should be expressible, testable, and useful before either is chosen.
- **Frameworks are tools, not partners.** Wrap them. Keep `import django` or `import axum::Router` out of the core. (Uncle Bob's 2014 "Framework Bound" is a full rant on this.)
- **Not every project needs four full circles.** Small projects may collapse Entities and Use Cases into one layer. The Dependency Rule still applies whatever the count.
- **The seams matter most.** Architecture lives at the boundaries between components. Defend them at every review — once they rot, replacing a dependency stops being a weekend task and becomes a six-month project.
- **Dialog from *A Little Architecture* (2016).** An aspiring architect says they want to make decisions about databases, frameworks, and webservers. Uncle Bob's response: "Oh. Well, then you don't want to become a Software Architect after all." The architect's job is to make decisions that let you **defer** the irrelevant decisions.
---
## Architecture and Agility
From "The Scatology of Agile Architecture" (2009): Agile does *not* mean no up-front architecture. The myth that you evolve architecture from zero is, in Uncle Bob's words, "horse shit." Good teams do enough architecture up front to get the seams right, then let the details emerge inside those seams. See [craft.md](craft.md) for more on this.
@@ -0,0 +1,135 @@
# The Craftsmanship Ethic
When to load this reference: when the task raises questions of professional judgment — estimation, deadline pressure, sloppy code accumulating, pairing, saying no to bad requests, or when the user invokes "technical debt" or "mess" or "craftsmanship."
The behaviors in *Clean Code* and *Clean Architecture* are not ends in themselves. They are instrumental to a larger ethic that Uncle Bob has been refining since the early 2000s: the software craftsmanship movement, which evolved into the Programmer's Oath (see [oath.md](oath.md)) and the 2022 book *Clean Craftsmanship*. This reference captures the non-code parts of that ethic that still materially affect how Claude should behave when writing or reviewing code.
---
## Clean Code Is a Practice, Not a Destination
From many posts, consolidated:
- Every function is an opportunity to practice. You don't reach "clean" and stop.
- The **Boy Scout Rule** is the daily discipline: leave each module cleaner than you found it, even if just by renaming one variable.
- "The only way to go fast is to go well." Dirty code does not trade speed for quality; it trades illusory short-term speed for enormous long-term slowness. This is the Productivity Roller-Coaster: feel fast for weeks, slow to a crawl over months.
- From *Going Fast*: "Fast" is a property you get by being disciplined, not by skipping discipline.
- From *Speed Kills*: conversely, the illusion that you can get fast by cutting corners almost always kills a project.
---
## A Mess Is Not Technical Debt
**This distinction matters.** People conflate them, and the conflation is a way to make sloppiness sound respectable.
**Ward Cunningham's Technical Debt (the original, 1992):** a **deliberate, considered** engineering trade-off when a schedule or learning situation justifies using a suboptimal design temporarily. You know what the right design is; you are choosing the wrong one now, *with intent*, and you will fix it later. Example: initial website uses server-rendered pages because there's no time to build an Ajax framework.
**A Mess:** bad code written by someone who did not do the work to understand the problem, did not refactor, did not test, did not think. It is not "debt" because it was never a considered choice — it is just poor craftsmanship.
From "A Mess is not a Technical Debt" (2009): calling a mess "technical debt" launders bad craftsmanship as if it were responsible engineering. It is not. When refusing to ship a mess, do not accept the framing that "we're just taking on some debt." Debt is deliberate; a mess is sloppy.
**Fowler's four quadrants of debt** (prudent/imprudent × deliberate/inadvertent) are a better map:
- Deliberate+prudent: the original Cunningham case ("we must ship now, we'll fix X next sprint").
- Deliberate+imprudent: "we don't have time for design" (toxic, not actually debt).
- Inadvertent+prudent: "now I know how we should have done it" (honest learning).
- Inadvertent+imprudent: plain-old-mess masquerading as debt.
---
## Saying No
From "Saying No!" (2009) and elaborated in *The Clean Coder*: professionals have an obligation to refuse impossible or unethical demands.
- When a manager asks for something that cannot be done correctly in the time allowed, the professional answer is "no, but here's what I can do," not "yes" followed by silent quality compromise.
- "Yes and then failing to deliver" is worse than "no" — the manager loses the ability to plan around reality.
- Professionals push back on their own estimates. If pressure makes you shorten a number you believe, you have stopped being the expert the organization pays you to be.
Applied to Claude: when a user asks for something that cannot be done well under the stated constraints (skip the tests, skip the error handling, ship something that will crash), the right response includes the pushback. Offer what you *can* deliver cleanly, not a degraded version of what was asked for.
---
## Honest Estimates
From "Why is Estimating so Hard?" (2012) and related posts:
- Estimates are **probability distributions, not numbers.** Give a range: optimistic, nominal, pessimistic. Three-point estimates are honest; single-point estimates almost always compress uncertainty.
- "I don't know yet, let me do a spike" is a professional answer. "I'll have it by Friday" said under duress without real confidence is not.
- An estimate is not a commitment; commitments come from negotiating after estimates are honestly given.
---
## On Documentation
**Martin's First Law of Documentation** (from *Agile Software Development: PPP*): "Produce no document unless its need is immediate and significant."
This is often misread as "Agile means no documentation." It does not. From the butunclebob.com wiki:
> "Agile Development is NOT development without documentation. Rejecting documentation in the name of 'Agility' is a flawed religious behavior. It is just as flawed as uncritically accepting the production of dozens of different documents."
Documentation, like any engineering activity, is prioritized by ROI. Create documents that more than pay back the effort to produce them. Skip documents written because policy requires them but no one will read them.
What counts as documentation:
- API docs (rustdoc, TSDoc, javadoc) — high value, close to code.
- Architecture decision records (ADRs) — capture *why* decisions were made.
- Onboarding / how-to guides — pay back every time a new person joins.
- Specs for important flows — pay back every time a flow breaks.
What does not:
- Status reports that recapitulate information already in the tracker.
- Design documents written after implementation that no one will read.
- Comments that restate the code.
---
## Pairing Guidelines
From "Pairing Guidelines" (2021) and earlier posts:
- Pairing is a **tool**, not a religion. Use it when it works; don't when it doesn't.
- Mature agile teams pair maybe 5070% of the time, not 100%.
- Some problems require "time, focus, and silence" to study before attacking. Pairing on those is worse than solo.
- Pair at the start of a story to align direction; solo for deep-focus passages; reunite to review.
- The strategy "separate the syntax issues from the semantic issues" is a useful pattern when stuck as a pair — refactor the mechanical noise (parsing, config, regex) into a helper module so the core algorithm can be reasoned about on its own.
---
## Shipping Under Pressure
From "AgilePeopleStillDontGetIt" (2006) and "We must ship now and deal with consequences" (2009):
- "It is completely unacceptable to release code that you aren't sure works. Either make sure it works, or don't ship it. Period."
- "A feature that crashes is much worse than a feature that doesn't exist. A feature that doesn't exist will defer revenue. A feature that crashes makes enemies out of customers."
- "Our customers interpret features as promises. When we release a feature we are promising that it works. When it crashes we have broken that promise."
- "Shipping untested software is shipping something unfinished and your customers will force you to finish it. The pressure will be higher at orders of magnitude if you finish it AFTER you have shipped it."
Applied to Claude: when asked to ship quickly and drop tests, the honest response is that the tests aren't slowing you down; they are the only way to ship correctly. "Going fast" without tests produces code that will return tenfold in debugging and firefighting over the next weeks.
---
## Professionalism Is Not Rigid Formalism
From "Why the sea is boiling hot" (2009) — the closing statement of Uncle Bob's 2009 Rails Conf keynote:
> "Professionalism does not mean rigid formalism. Professionalism does not mean adhering to bureaucracy. Professionalism is **honor**. Professionalism is being honest with yourself and disciplined in the way you work. Professionalism is not letting fear take over."
Honor and discipline. Not process for its own sake. The rules in this skill are tools for being disciplined; they are not a rulebook to hide behind.
---
## The Tricky Bit
From "The Tricky Bit" (2010): a British MP flew the Concorde and complained to the designer that going supersonic "didn't feel any different at all." The designer beamed: "Yes, that was the tricky bit."
Clean code, good architecture, solid tests — when they are working, the reader doesn't notice. The absence of friction is the product. Code that *announces* how clever it is, how much architecture it has, how sophisticated its patterns are, is usually the opposite of clean. The goal is invisibility — the reader moves through the code and feels nothing but understanding.
---
## When Claude Should Invoke Any of This
- **User wants to skip tests "just this once":** reference the "A Mess is not Debt" framing and the shipping-under-pressure material.
- **User wants a speculative number instead of a range:** offer a range and explain why.
- **User wants you to document something they won't read:** suggest the minimum viable doc that pays its way.
- **User wants a "quick fix" that you can see will rot the module:** explain the Boy Scout Rule cost — a quick fix that makes the code worse is a negative-value change even at zero time cost.
- **User says "we're doing Agile, we don't write documentation":** redirect to Martin's First Law and the "it's about ROI" framing.
The oath ([oath.md](oath.md)) captures the promises. This file captures the attitude and the vocabulary for navigating the hard conversations where craft meets pressure.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,161 @@
# Programming Paradigms
When to load this reference: when choosing between procedural and OO style, writing code in a functional language, refactoring switch statements, handling persistence, or when the user asks about OO vs FP, design patterns, or Clean Code's chapter on objects and data structures.
Uncle Bob's reductionist framing of the three paradigms is a powerful lens for reasoning about code shape. Each paradigm imposes **discipline** by **taking something away** from the programmer.
---
## The Three Paradigms
Each paradigm is defined by what it *forbids*, not by what it enables. This is Dijkstra-style reasoning: fewer primitives mean fewer ways to be wrong.
### Structured Programming
- **Forbids:** `goto` (direct transfer of control).
- **Provides:** Sequence, Selection (if/else), Iteration (while). Dijkstra proved any algorithm can be expressed with just these three.
- **Why:** Dijkstra's 1968 letter "Go To Statement Considered Harmful." Unrestricted `goto` makes programs impossible to reason about. Restricted control flow is provably correct for sequence, selection, iteration; not provably correct with arbitrary `goto`.
- **Status today:** Won so completely that most developers don't even realize they're using it. Modern languages don't have `goto` (or discourage it).
### Object-Oriented Programming
- **Forbids:** Raw function pointers / indirect transfer of control through unmanaged pointers.
- **Provides:** Polymorphism. The language manages the function pointers for you.
- **Why:** Raw function pointers (as in C) are correct but fragile — every caller must follow conventions every time. Polymorphism provides the same runtime capability through a disciplined mechanism: objects carry their own dispatch table, set up once when the object is created.
- **The reductionist core:** OO = polymorphism. Encapsulation, methods-bound-to-data, and simple inheritance exist in C and Pascal too. **What OO uniquely gives you is convenient polymorphism.** "OO without polymorphism is not OO."
### Functional Programming
- **Forbids:** Assignment / mutation of state.
- **Provides:** Referential transparency. Same inputs → same outputs, always, everywhere.
- **Why:** Shared mutable state is the source of most concurrency bugs and most "action at a distance" reasoning failures. Forbidding it means state changes are explicit and localized.
- **The reductionist core:** FP = referential transparency. Higher-order functions exist in OO languages too (Smalltalk, etc.). What FP uniquely gives you is the guarantee that a function call cannot change anything you didn't pass to it.
### Why "Three Paradigms" Matters
These are **orthogonal**, not competing. Each removes a different freedom:
| Paradigm | Discipline on | Mechanism |
|---|---|---|
| Structured | Direct transfer of control | No `goto` |
| OO | Indirect transfer of control | Polymorphism |
| FP | Assignment | Referential transparency |
A language can (and modern ones often do) impose all three disciplines at once. You can write OO code functionally, and you can apply SOLID inside a functional program.
---
## OO and FP Are Orthogonal, Not Exclusive
From Uncle Bob's 2014 and 2018 "FP vs OO" posts:
> "The principles of software design still apply, regardless of your programming style. The fact that you've decided to use a language that doesn't have an assignment operator does not mean that you can ignore the Single Responsibility Principle; or that the Open Closed Principle is somehow automatic."
And from his 2023 *Functional Classes* post: "Should you subdivide a functional program into classes the way you would an object oriented program? Yes. You should. Because the rules don't change just because you've chosen to use immutable data structures."
**A class, reductively:** "A group of cohesive and narrowly defined functions that operate on an encapsulated data structure. The functions may, or may not, be polymorphically deployed." This definition works in Clojure, Haskell, Rust, Java, TypeScript, Python.
**The design principles transcend paradigm:**
- SRP applies in Clojure (group functions by actor).
- OCP applies in Haskell (use abstraction, add type class instances).
- DIP applies anywhere there are modules.
- A "class" in the sense above is a cohesive namespace of related functions plus the data they operate on.
---
## Data/Object Anti-Symmetry
From Chapter 6 of *Clean Code* and elaborated in the 2019 blog post "Classes vs. Data Structures."
**Two definitions that complement each other:**
- **Object:** A set of functions that operate on **implied** data. Data exists but is hidden. Callers see only functions.
- **Data structure:** A set of data elements operated on by **implied** functions. Data is exposed. Functions exist but are not specified by the structure.
They are **diametric opposites**. You cannot fully be both.
### Consequences
- **DTOs are data structures, not objects.**
- **Database tables are data structures, not objects.**
- **"ORM" is a misnomer.** There is no mapping between database tables and objects. ORMs map tables to data structures. (This is not pedantic; it explains why ORMs have the smells they do.)
- **Polymorphism is the marker of objects.** When `shape.area()` dispatches dynamically to the Circle or Square implementation, you are doing OO. When `area(shape)` is a free function with `match shape { Circle => …, Square => … }`, you are doing procedural work.
### The Four Symmetry Rules
These tell you when to choose each style.
| | Add new FUNCTION | Add new TYPE |
|---|---|---|
| **Classes (OO)** | **Hard** — change every class | **Easy** — add one class |
| **Data structures (procedural)** | **Easy** — add one function | **Hard** — change every function |
**Choose by expected axis of change:**
- If you expect more new functions than new types → procedural style with data structures + functions (e.g., visitor pattern, pattern matching over enums, Clojure-style).
- If you expect more new types than new functions → OO style with classes and polymorphism.
- The **Visitor pattern** is procedural-style behavior over OO data — it bridges the two.
**In Rust specifically:** enums with `match` are procedural by this taxonomy (add a variant → every match must handle it); traits with implementations are OO (add an impl → no existing code changes). Neither is wrong; choose by axis of change. If new variants are rare and new operations are common, the enum wins. If new types are common, the trait wins.
---
## Polymorphism and if-else-switch
From "if-else-switch" (2021). A very common refactor:
**The pattern.** When you see an if/else chain or switch that branches by type or by "kind," replace it with:
1. A base class or interface with one method per case.
2. Concrete implementations, one per branch.
3. A **factory** that creates the right implementation based on the discriminator (this is where the if/else/switch ends up, condensed into one place).
4. The business logic calls the interface, never the discriminator.
**Runtime characteristics are identical.** If/else does a procedural lookup, switch uses a compiler-built jump table, polymorphic dispatch uses a vtable — similar performance.
**What you gain:**
- The high-level business code no longer transitively depends on every low-level case.
- Each case is its own named method, not an indented block within a branch.
- New cases = new classes (OCP).
- Independent deployment becomes possible: the high-level module and each implementation can live in separate components.
**When not to apply:** if the switch is small, stable, and not type-based (e.g., processing a small enum of flags in one place), leaving it as a switch is fine. The rule is "factor out switches on *type*," not "destroy every conditional."
---
## The Tell-Don't-Ask Style
Alan Kay's original OO conception: objects as cells in a biological system.
> "Neurons are tellers, not askers. Hormones are tellers, not askers. In biological systems, communication was half-duplex."
Instead of:
```
if account.getBalance() < amount:
throw InsufficientFunds
account.setBalance(account.getBalance() - amount)
```
Say:
```
account.withdraw(amount) // account decides if it can, and how
```
The caller stops interrogating state and deciding. The object owns the decision. This is what Law of Demeter is a weak shadow of — the deeper principle is that state should not leak out of objects.
---
## Loops and State Machines
From the 2020 "Loopy" post. Any program with nested loops can be refactored step-by-step into a Turing-style finite state machine, with tests passing at every step. This is a useful mental exercise: a nested loop is a state machine that a programmer wrote too compactly.
Practical takeaway: when a loop body is getting complex, consider extracting an explicit state (enum of states) and transitioning between them. Reads better than four nested `if`s; generalizes better; easier to test.
---
## Applying This in Practice
- **Default to OO + polymorphism** for business logic where types vary (entities, strategies, handlers). Polymorphism is the mechanism behind DIP, OCP, and Clean Architecture boundaries.
- **Default to data structures + free functions** for values, messages, and records that flow through the system. DTOs, events, API payloads, DB rows.
- **Keep the two species apart.** A "hybrid" that has both public fields and rich behavior usually gets the worst of both worlds.
- **FP is not an exception to SOLID.** Cohesion, SRP, DIP all still apply; you express them with namespaces, protocols, or type classes instead of classes.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,177 @@
# Test Driven Development
When to load this reference: when writing new tests, reviewing tests, debugging brittle tests, dealing with legacy code that resists testing, or deciding on a testing strategy for a module.
Tests are the safety net that makes fearless refactoring possible. Without that net, every change is a gamble; with it, every change can be confident. Tests are also the most precise, executable documentation a system will ever have.
**Michael Feathers's definition of legacy code:** *Legacy code is code without tests.* Uncle Bob adopted this definition and it underpins the TDD practice.
---
## The Three Laws of TDD
1. **You are not allowed to write any production code unless it is to make a failing unit test pass.**
2. **You are not allowed to write any more of a unit test than is sufficient to fail — and compilation failures are failures.**
3. **You are not allowed to write any more production code than is sufficient to pass the one failing unit test.**
The loop is measured in seconds, not minutes. Write a line or two of test, see it fail, write a line or two of production, see it pass, repeat. This is the **nano-cycle**.
**Why these rules:**
- **Debugging time plummets** — you were never more than 60 seconds away from working code.
- **Tests are automatic documentation** that cannot fall out of sync with the system.
- **Design improves** because code written to be testable is naturally decoupled.
- **Refactoring becomes fearless** because the net catches regressions instantly.
This is double-entry bookkeeping for software. Every behavior is stated twice — once in the test, once in the code — and they must agree.
---
## F.I.R.S.T. — Clean Tests
Clean tests are:
- **Fast.** Slow tests will stop being run. If a suite takes 10 minutes, people will commit without running it. 15-minute CI feedback is too slow for the TDD loop.
- **Independent.** No test depends on another. Any test can run alone, in any order.
- **Repeatable.** Same result in every environment — laptop, CI, staging. If a test depends on the network, wall clock, or shared database, it is flaky and must be fixed.
- **Self-validating.** Pass or fail. No manual inspection.
- **Timely.** Written *just before* the production code they cover — not "when we have time."
Test code is first-class. Hold it to the same clarity bar as production code. When tests rot, production code rots.
---
## Canonical Test Definitions (First-Class Tests, 2017)
The industry has been sloppy about what "unit," "integration," "acceptance," etc. mean. Uncle Bob's proposed taxonomy:
- **Unit Test.** Written by a programmer, for a programmer. Ensures production code does what the programmer expected. Sometimes called **programmer test** or **micro-test**.
- **Acceptance Test.** Written by the business (or a BA/QA representing the business). Ensures production code does what the business expects. Sometimes called **customer test**.
- **Integration Test.** Written by architects or technical leads. Ensures a sub-assembly of system components operates correctly. **These are plumbing tests, not business-rule tests** — rules are already verified by unit and acceptance tests.
- **System Test.** An integration test for the whole integrated system.
- **Micro-test** (Mike Hill / @GeePawHill). A unit test at very small scope — tests a single function or small group.
- **Functional Test.** A unit test at larger scope, with mocks for slow components.
> "Integration tests do not test business rules. Those rules have already been tested, once by programmer (unit) tests, and again by customer (acceptance) tests. Integration tests test the plumbing and choreography of the components." — Uncle Bob (Twitter, 2019)
**Implication for Claude when writing tests:** Know which kind of test you are writing and don't couple it to the wrong kind. If you're asked to "add tests" for a pure function, write unit/micro tests. If you're asked to "test the API works end-to-end," that's integration/system. Don't test business rules in an integration test — the rules should already have unit tests.
---
## Test Structure
Use one of these structures; be consistent.
- **Arrange / Act / Assert** — set up context, perform action, check result.
- **Given / When / Then** — same thing in BDD vocabulary.
- **Build / Operate / Check** — same thing, different vocabulary.
One *concept* per test. Often one assertion, but "one concept" is the real rule — several assertions verifying the same behavior are fine.
### Test Naming
Name the test for what it verifies about behavior, not for the method. `returns_empty_list_when_given_empty_input` beats `test_filter_1`. If the name runs long, the test is probably doing more than one thing.
---
## Test Doubles — The Hierarchy
Adapted from Gerard Meszaros's *xUnit Patterns*, with Uncle Bob's gloss. Each is a degree of sophistication above the last.
- **Dummy.** Passed around but never used. Fills a parameter slot.
- **Stub.** Returns canned answers. No logic.
- **Spy.** A stub that records the calls it received.
- **Mock.** A spy with expectations built in: set up *before* the act, verified *after*. Fails if expected interactions didn't happen.
- **Fake.** A working implementation with production-unfit shortcuts — e.g., in-memory repo that stands in for a real database.
Pick the lowest-sophistication double that does the job. A mock where a stub would suffice adds coupling and fragility.
**Uncle Bob hand-rolls most of his Java mocks** ("Manual Mocking," 2009) rather than using mockito, to keep explicit control over ceremony. This is a taste preference, not a rule, but his reasoning (less magic, clearer test code) is worth knowing.
---
## Chicago vs. London (State-ism vs. Mockism)
Two schools of TDD.
- **Chicago / Classical / State-ist.** Test behavior through state. Exercise the object, assert on its final state (or collaborators' state). Minimal mocking. Less coupled to implementation detail.
- **London / Mockist.** Test behavior through interactions. Mock collaborators; assert on calls. More explicit about collaboration but more coupled to it.
**Practical guidance:** Use Chicago for value objects, algorithms, internal logic. Use London at **boundaries** — where the code coordinates external collaborators. Never mock what you own when you could exercise it directly; mock (or fake) what you do not own when the real thing would make the test slow or flaky.
---
## Fragile Tests
Tests that break without a real regression are worse than no tests — they train developers to ignore the suite. Known causes:
- **Interface sensitivity.** Tests break because a signature changed, not behavior. Often a sign of excessive mocking.
- **Behavior sensitivity.** Tests break because an unrelated behavior changed. A sign of poor isolation.
- **Data sensitivity.** Tests break because shared fixtures changed. Fix by making tests own their data.
- **Context sensitivity.** Tests pass locally, fail in CI. Remove environmental coupling: clock, network, filesystem, time zone.
- **Over-specification.** Tests assert on more than the behavior under test — internal call order, private fields, log output. Assert on what the *user of the code* would observe.
A fragile test is a design signal — usually a missing abstraction, a leaky boundary, or an over-eager mock.
"Skilled TDDers understand that neither micro-tests, nor functional tests, nor acceptance tests should be coupled to the implementation of the system." — *First-Class Tests* (2017)
---
## As Tests Get More Specific, Code Gets More Generic
Uncle Bob's formulation (2009): tests are specifications. As you add tests, the specifications grow more specific. To satisfy them all, the production code must grow more *generic*. This is the inverse relationship that drives TDD-induced good design — the code gets pushed toward abstractions that cover many cases rather than one.
---
## The Transformation Priority Premise (TPP)
When making a failing test pass, there is a natural ordering of changes, simpler before more complex. Prefer earlier transformations when more than one would work:
1. `{} → nil` — no code → returning nil
2. `nil → constant` — return a constant
3. `constant → variable` — replace constant with a variable
4. `statement → statements` — add another statement
5. `unconditional → if` — introduce a branch
6. `scalar → array` — move from a single value to a collection
7. `array → container` — move to a richer collection type
8. `statement → recursion` — replace a statement with recursion
9. `if → while` — replace a branch with iteration
10. `expression → function` — extract a function
11. `variable → assignment` — introduce mutation
Using lower-priority transformations earlier creates needless complexity; using higher-priority ones later often indicates a design that could be simpler. TPP is a tiebreaker, not a law — but it usually guides tests toward algorithms that generalize cleanly.
---
## The Cycles of TDD
TDD operates at multiple time scales simultaneously. Working at only one scale produces bad software.
- **Seconds (Red-Green-Refactor).** The nano-cycle.
- **Minutes (Specific-to-Generic).** Tests grow more specific; code grows more generic.
- **Tens of minutes (Boundary).** Periodically step back and ask whether the module is still well-factored. Extract. Rename. Regroup.
- **Hours (Architecture).** Once a day or so, step back further: are the component boundaries still correct? Does the Dependency Rule still hold?
- **Days (Acceptance).** Acceptance tests (at the feature/use-case level) close the loop with the business.
Skipping the larger cycles is the most common failure mode. Red-Green-Refactor religiously, but never step back to reconsider architecture, and you end up with a suite of fine-grained tests wrapped around a tangled ball of mud.
---
## Testing Across Architectural Boundaries
- **The test boundary** is a first-class part of architecture. Tests live outside the system they test.
- **Do not couple tests to UI frameworks or databases.** If a test needs a browser to exercise a use case, the boundary between use case and UI is broken.
- **Legacy code strategy** (Feathers). Find a seam — a place where behavior can be varied without modifying code. Write a characterization test at that seam to pin down current behavior. Refactor behind the pin. Repeat.
Uncle Bob's position on test placement: "Don't test through UIs. Don't test through web servers. Test as close to the code as you can." — *Testing Like the TSA* (2017)
---
## Common Pitfalls
- **Writing tests after the fact.** Produces tests that confirm whatever the code happens to do, including the bugs. Much lower value than TDD.
- **Slow test suites.** If any unit test takes more than a fraction of a second, isolate it. Keep the unit suite fast and run integration tests separately.
- **Mocking what you own.** Prefer real objects for your own code.
- **Testing implementation details.** Refactors then break tests without any real regression, and people conclude "TDD gets in the way of refactoring." It doesn't — the tests were just wrong.
- **Skipping refactor.** Red-Green-… is not TDD. The third step is where design emerges.
- **Over-coverage religion.** Uncle Bob's ratio for some project types: 20% test-first, 80% test-after is acceptable for controllers/models/views (per *Testing Like the TSA*, 2017). The three laws are guidance for the hottest logic in the system, not dogma for every trivial accessor.
@@ -0,0 +1,87 @@
---
name: commit-convention
description: Enforce commit message convention for features, fixes, chores, and docs
---
# Commit Message Convention
All commit messages MUST follow one of the four formats below. The type is lowercase. The subject is imperative, lowercase, and has no trailing period.
## Types
### `feat` — New feature or new behaviour
Scope is required: the name of the affected module.
```
feat(<module>): <what changed or what feature>
```
Examples:
```
feat(auth): add google oauth sign-in
feat(billing): support multi-currency invoices
feat(users): allow avatar upload
```
### `fix` — Bug fix or issue resolution
Scope is required: the name of the affected module.
```
fix(<module>): <what was fixed>
```
Examples:
```
fix(auth): prevent token refresh race condition
fix(cart): correct total when discount is zero
fix(api): return 404 instead of 500 on missing user
```
### `chore` — Housekeeping, dependency bumps, config changes
No scope.
```
chore: <what was adjusted>
```
Examples:
```
chore: adjust package.json version (bump)
chore: update eslint config
chore: remove unused devDependency
```
### `docs` — Documentation only
No scope.
```
docs: <what changed>
```
Examples:
```
docs: add setup guide to README
docs: document commit convention
docs: clarify env variable defaults
```
## Rules
1. Type is ALWAYS lowercase (`feat`, `fix`, `chore`, `docs`).
2. `feat` and `fix` REQUIRE a module scope in parentheses.
3. `chore` and `docs` do NOT use a scope.
4. Subject line is imperative mood ("add", not "added" or "adds").
5. Subject line is lowercase, no trailing period.
6. Keep the subject under ~72 characters.
7. If a commit needs multiple types, split it into multiple commits.
## Choosing the right type
| Situation | Type |
|-----------|------|
| New user-facing capability | `feat` |
| New internal behaviour | `feat` |
| Something was broken, now works | `fix` |
| Version bump in package.json | `chore` |
| Lockfile regeneration, config tweak | `chore` |
| README, guide, or comment-only change | `docs` |
@@ -0,0 +1,449 @@
---
name: kana-rust-backend-best-practice
description: Reference guide for building a Rust clean-architecture backend with Axum, SeaORM, Argon2, JWT, and sea-orm-migration. Use when scaffolding a new Rust service, adding a feature (domain + use-case + repository + handler), or reviewing Rust code against the axum-clean-architecture reference layout.
---
# Axum Clean Architecture Skill
Reference stack (see `../axum-clean-architecture`):
| Layer | Tech |
|---|---|
| HTTP framework | Axum 0.8 |
| ORM | SeaORM 1.1 (PostgreSQL via sqlx + rustls) |
| Migrations | sea-orm-migration |
| Auth | Argon2 (password hashing) + jsonwebtoken (JWT) |
| Validation | zod-rs (schema-driven, mirrors Zod) |
| Pagination | paginator-rs + paginator-sea-orm + paginator-axum |
| Observability | tracing + tracing-subscriber |
| Middleware | tower-http (CORS, TraceLayer) |
| Runtime | Tokio (full features) |
| Error handling | anyhow (app-level), typed domain errors |
---
## 0. Workspace layout
```
axum-clean-architecture/
├── Cargo.toml # workspace, resolver = "3"
├── apps/
│ ├── iam/ # core domain library (lib crate)
│ │ └── src/
│ │ ├── domain/ # entities, repository traits, domain errors
│ │ ├── application/ # use cases + port traits
│ │ ├── infrastructure/ # SeaORM repos + auth services
│ │ └── presentation/ # Axum handlers, DTOs, middleware, state
│ ├── gateway/ # binary — assembles router, runs server
│ └── bootstrap/ # binary — seeds permissions/roles/admin
├── .config/ # AppServer, database/env helpers
└── .migrations/ # sea-orm-migration crate
```
The `iam` app is a **library crate**. `gateway` and `bootstrap` depend on it.
---
## 1. Dependency rules (strictly enforced)
```
presentation → application → domain
infrastructure → domain (implements domain traits)
presentation → infrastructure (only to wire AppState)
```
- Domain has **zero** external crate dependencies beyond `uuid`, `chrono`.
- Use cases depend only on port traits — never on concrete infrastructure types.
- Presentation instantiates use cases from `AppState` on every request; use cases are not stored.
---
## 2. Domain layer
### Entity pattern
Plain Rust structs — no derives beyond what domain logic needs. No ORM annotations.
```rust
// domain/user/entity.rs
pub struct User {
pub id: Uuid,
pub email: String,
pub password_hash: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
pub struct NewUser {
pub id: Uuid,
pub email: String,
pub password_hash: String,
}
#[derive(Default)]
pub struct UserPatch {
pub email: Option<String>,
pub password_hash: Option<String>,
}
```
### Repository trait pattern
Use `impl Future` in trait methods (Rust 2024 edition, no `async_trait` needed).
Always `Send + Sync` on the trait.
```rust
// domain/user/repository.rs
pub trait UserRepository: Send + Sync {
fn find_by_id(&self, id: Uuid)
-> impl Future<Output = Result<Option<User>, RepositoryError>> + Send;
fn find_by_email(&self, email: &str)
-> impl Future<Output = Result<Option<User>, RepositoryError>> + Send;
fn create(&self, user: NewUser)
-> impl Future<Output = Result<User, RepositoryError>> + Send;
fn update(&self, id: Uuid, patch: UserPatch)
-> impl Future<Output = Result<User, RepositoryError>> + Send;
fn delete(&self, id: Uuid)
-> impl Future<Output = Result<(), RepositoryError>> + Send;
fn list(&self, params: &PaginationParams)
-> impl Future<Output = Result<PaginatorResponse<User>, RepositoryError>> + Send;
}
```
### Shared RepositoryError (lives in domain)
```rust
pub enum RepositoryError {
NotFound,
Conflict(String),
Database(String),
}
```
### Domain errors
Per-aggregate. `AuthError` lives in `domain/auth/errors.rs`:
```rust
pub enum AuthError {
InvalidCredentials,
EmailAlreadyExists,
UserNotFound,
PasswordHashFailed(String),
PasswordVerificationFailed(String),
TokenGenerationFailed(String),
InvalidToken(String),
RepositoryError(String),
}
```
---
## 3. Application layer
### Port traits (interfaces for external services)
```rust
// application/auth/ports/password.rs
pub trait PasswordService: Send + Sync {
fn hash(&self, password: &str)
-> impl Future<Output = Result<String, PasswordError>> + Send;
fn verify(&self, password: &str, hash: &str)
-> impl Future<Output = Result<bool, PasswordError>> + Send;
}
```
```rust
// application/auth/ports/token.rs
pub trait TokenService: Send + Sync {
fn generate_auth_tokens(&self, sub: &str)
-> impl Future<Output = Result<(String, String), TokenError>> + Send;
fn verify_access_token(&self, token: &str)
-> Result<String, TokenError>;
}
```
### Use case pattern
Generic over port traits and repository traits. Constructed in the handler, not stored.
```rust
// application/user/use_cases/create.rs
pub struct CreateUserCommand { pub email: String, pub password: String }
pub struct CreateUserUseCase<P, R> {
password_service: P,
user_repository: R,
}
impl<P: PasswordService, R: UserRepository> CreateUserUseCase<P, R> {
pub fn new(password_service: P, user_repository: R) -> Self { ... }
pub async fn execute(&self, cmd: CreateUserCommand) -> Result<User, AuthError> {
// 1. guard: check uniqueness
// 2. hash password via port
// 3. create domain entity with Uuid::new_v4()
// 4. persist via repository
// 5. log + return
info!(user_id = %user.id, "user created");
Ok(user)
}
}
```
### Use case naming convention
| File | Struct | Command/Query |
|---|---|---|
| `create.rs` | `CreateXxxUseCase` | `CreateXxxCommand` |
| `update.rs` | `UpdateXxxUseCase` | `UpdateXxxCommand` |
| `delete.rs` | `DeleteXxxUseCase` | `DeleteXxxCommand` |
| `detail.rs` | `XxxDetailUseCase` | `XxxDetailQuery` |
| `list.rs` | `ListXxxsUseCase` | takes `&PaginationParams` |
---
## 4. Infrastructure layer
### SeaORM repository implementation
```rust
// infrastructure/repository/user.rs
#[derive(Clone)]
pub struct SeaOrmUserRepository { db: DatabaseConnection }
// Convert ORM Model → domain entity here (not in domain)
impl From<Model> for User { ... }
// Map DbErr → RepositoryError
fn map_db_err(e: DbErr) -> RepositoryError {
match e {
DbErr::RecordNotFound(_) => RepositoryError::NotFound,
other => { error!(error = %other, "database operation failed"); RepositoryError::Database(other.to_string()) }
}
}
impl UserRepository for SeaOrmUserRepository {
async fn create(&self, user: NewUser) -> Result<User, RepositoryError> {
let model = ActiveModel {
id: Set(user.id),
email: Set(user.email),
password_hash: Set(user.password_hash),
created_at: Set(now),
updated_at: Set(now),
};
let inserted = model.insert(&self.db).await.map_err(|e| match e {
DbErr::Exec(ref msg) | DbErr::Query(ref msg)
if msg.to_string().contains("unique") =>
RepositoryError::Conflict("email already exists".into()),
other => RepositoryError::Database(other.to_string()),
})?;
Ok(User::from(inserted))
}
}
```
Pagination uses `paginator-sea-orm`:
```rust
let response = UserEntity::find()
.paginate_with(&self.db, params)
.await
.map_err(|e| RepositoryError::Database(e.to_string()))?;
let mapped: Vec<User> = response.data.into_iter().map(User::from).collect();
Ok(PaginatorResponse { data: mapped, meta: response.meta })
```
### Auth services
- `Argon2PasswordService`: uses `spawn_blocking` for CPU-bound hashing, `SaltString::generate(OsRng)`.
- `JwtTokenService`: stores `secret: Vec<u8>`, generates separate access/refresh tokens with a `type` claim. `verify_access_token` checks `claims.token_type == "access"`.
### SeaORM entities (ORM models)
Live in `infrastructure/repository/entities/`. One file per table. Junction tables (`user_role`, `role_permission`) have composite primary keys. Timestamps use `DateTimeWithTimeZone`.
---
## 5. Presentation layer
### AppState
Concrete types only — no trait objects. Cheap to clone because `DatabaseConnection` is internally Arc-backed.
```rust
#[derive(Clone)]
pub struct AppState {
pub password_service: Argon2PasswordService,
pub token_service: JwtTokenService,
pub user_repository: SeaOrmUserRepository,
pub role_repository: SeaOrmRoleRepository,
pub permission_repository: SeaOrmPermissionRepository,
}
```
Injected via `Extension(state)` on every handler. Use cases are constructed inside handlers.
### AppError
```rust
pub enum AppError { BadRequest(String), Unauthorized, Forbidden, NotFound, Conflict(String), Internal(String) }
impl IntoResponse for AppError { /* maps to HTTP status + JSON { "error": "..." } */ }
impl From<AuthError> for AppError { ... }
impl From<RepositoryError> for AppError { ... }
impl From<TokenError> for AppError { ... }
```
Internal errors are logged with `tracing::error!` before returning a generic 500 message.
### Handler pattern
```rust
#[instrument(skip_all, fields(actor = %actor.id, email = %req.email))]
pub async fn create(
Extension(state): Extension<AppState>,
Extension(actor): Extension<AuthenticatedUser>,
Json(req): Json<CreateUserRequest>,
) -> Result<(StatusCode, Json<UserResponse>), AppError> {
let use_case = CreateUserUseCase::new(
state.password_service.clone(),
state.user_repository.clone(),
);
let user = use_case.execute(req.into()).await?;
Ok((StatusCode::CREATED, Json(user.into())))
}
```
Rules:
- Always `#[instrument(skip_all, fields(...))]` on every handler.
- Use `?` to propagate `AppError` (via `From` impls).
- `201 CREATED` for `POST`, `204 NO_CONTENT` for `DELETE`, `200 OK` for everything else.
- Pagination handlers return `PaginatedJson<Dto>` via `paginator-axum`.
### DTO pattern
```rust
#[derive(Debug, Serialize, Deserialize, ZodSchema)]
pub struct CreateUserRequest {
#[zod(email)]
pub email: String,
#[zod(min_length(8), max_length(128))]
pub password: String,
}
impl From<CreateUserRequest> for CreateUserCommand { ... }
#[derive(Debug, Serialize)]
pub struct UserResponse { pub id: Uuid, pub email: String, pub created_at: DateTime<Utc>, pub updated_at: DateTime<Utc> }
impl From<User> for UserResponse { ... }
```
- Request structs: `Deserialize + ZodSchema`. Use `#[zod(...)]` for field-level validation.
- Response structs: `Serialize` only. Never expose `password_hash`.
- Conversions: `impl From<Request> for Command` and `impl From<DomainEntity> for Response`.
### Middleware
**Auth middleware** (`presentation/middleware/auth.rs`):
- Extracts `Bearer <token>` from `Authorization` header.
- Calls `state.token_service.verify_access_token(token)`.
- Inserts `AuthenticatedUser { id: Uuid }` into request extensions.
**Permission check** (`presentation/middleware/permission.rs`):
- Called inline from handlers: `ensure_permission(&state, &actor, "users:write").await?`.
- Queries `permission_repository.find_for_user(actor.id)` and checks by name.
### Router assembly
```rust
pub fn build_router(state: AppState) -> Router {
Router::new()
.nest("/auth", auth::router())
.nest("/me", me::router())
.nest("/users", user::router())
.nest("/roles", role::router())
.nest("/permissions", permission::router())
.layer(Extension(state))
}
```
Gateway nests the IAM router at `/api/v1/iam` and adds a health check at `/`.
---
## 6. Migrations (sea-orm-migration)
```rust
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.create_table(
Table::create()
.table(Users::Table)
.if_not_exists()
.col(ColumnDef::new(Users::Id).uuid().not_null().primary_key())
.col(ColumnDef::new(Users::Email).string().not_null().unique_key())
...
.to_owned(),
).await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.drop_table(Table::drop().table(Users::Table).to_owned()).await
}
}
#[derive(DeriveIden)]
pub enum Users { Table, Id, Email, PasswordHash, CreatedAt, UpdatedAt }
```
Naming convention: `m{YYYYMMDD}_{6-digit-seq}_{description}.rs`, e.g. `m20260413_000001_create_users.rs`.
---
## 7. Bootstrap pattern
A separate `bootstrap` binary seeds idempotent system data (permissions, roles, admin user):
```rust
// Check existence before inserting — fully idempotent
if permission_repo.find_by_name("rbac:manage").await?.is_none() {
permission_repo.create(...).await?;
}
```
Standard permissions:
- `rbac:manage`, `users:read`, `users:write`, `roles:read`, `roles:write`, `permissions:read`, `permissions:write`
---
## 8. Adding a new aggregate (checklist)
1. **Domain**: `domain/{name}/entity.rs` (entity + NewXxx + XxxPatch), `domain/{name}/repository.rs` (trait + errors), `domain/{name}/errors.rs` if needed.
2. **Application**: `application/{name}/mod.rs`, `application/{name}/use_cases/{create,update,delete,detail,list}.rs`.
3. **Infrastructure entity**: `infrastructure/repository/entities/{name}.rs` (SeaORM model).
4. **Infrastructure repo**: `infrastructure/repository/{name}.rs` (`From<Model>`, `impl XxxRepository for SeaOrmXxxRepository`).
5. **Add to AppState**: `{name}_repository: SeaOrmXxxRepository`.
6. **Presentation DTO**: `presentation/{name}/dto.rs` (Request + Response with `From` impls).
7. **Presentation handlers**: `presentation/{name}/handlers.rs` (`#[instrument]`, construct use case, return DTO).
8. **Presentation router**: `presentation/{name}/mod.rs` (define routes with `axum_route_macro` or `Router::new().route(...)`).
9. **Nest in `build_router`**.
10. **Migration**: new file in `.migrations/src/` following naming convention.
11. **Bootstrap**: seed any required initial data.
---
## 9. Key conventions
- Edition **2024** — use `impl Future` in traits, not `#[async_trait]`.
- All timestamps are `DateTime<Utc>` in domain; `DateTimeWithTimeZone` in SeaORM models; convert with `.with_timezone(&Utc)`.
- UUIDs generated with `Uuid::new_v4()` in the use case, not the repository.
- Unique-constraint conflicts detected via string match on `DbErr::Exec`/`DbErr::Query` containing `"unique"` — map to `RepositoryError::Conflict`.
- `tracing::instrument` on every handler; log user/actor IDs as structured fields.
- `warn!` for expected failures (wrong password, permission denied), `error!` for unexpected DB errors.
- Response structs never expose internal fields (`password_hash`, internal IDs from junction tables).
@@ -0,0 +1,107 @@
---
name: push-flow-convention
description: Enforce pre-commit/pre-push hooks, lint-staged checks, and semver version bump on every push
---
# Push Flow Convention
Every repository MUST enforce the same pre-commit, pre-push, and versioning flow. No push lands without hooks, lint-staged, and a version bump.
## Required Setup
### 1. Lefthook (pre-commit + pre-push)
> **Always use [Lefthook](https://lefthook.dev/) for git hooks. Never use husky.**
Install once per repo:
```bash
pnpm add -D lefthook lint-staged
pnpm exec lefthook install
```
Create `lefthook.yml` in project root:
```yaml
pre-commit:
commands:
lint-staged:
run: pnpm exec lint-staged
pre-push:
commands:
lint-staged:
run: pnpm exec lint-staged --diff="origin/{push_remote_branch}...HEAD"
bump:
run: pnpm run bump
```
### 2. lint-staged
Declared in `package.json`. Runs ONLY on staged files so commits stay fast.
```json
{
"lint-staged": {
"*.{ts,tsx,js,jsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md,yml,yaml}": [
"prettier --write"
]
}
}
```
### 3. Version bump script
`package.json` MUST expose a `bump` script used by `pre-push`:
```json
{
"scripts": {
"bump": "node scripts/bump-version.mjs"
}
}
```
The script inspects the diff between the current branch and its upstream, applies the semver rule below, and writes the new version back to `package.json`. Commit the bump before pushing (amend the previous commit or create a `chore: adjust package.json version (bump)` commit — see `commit-convention`).
## Semver Rules (applied on every push)
The bump is based on the changes in the commits being pushed:
| Change size / kind | Bump |
|--------------------|------|
| `< 5` changed files across pushed commits | **patch** (`x.y.Z`) |
| `>= 5` changed files across pushed commits | **minor** (`x.Y.0`) |
| New feature OR new behaviour (any `feat:` commit) | **major** (`X.0.0`) |
Rules in order of precedence:
1. If ANY commit being pushed is a `feat(...)`**major** bump.
2. Otherwise, count files changed (`git diff --name-only origin/<branch>...HEAD | wc -l`):
- fewer than 5 → **patch**
- 5 or more → **minor**
The `feat` rule always wins — a new feature is always a major bump regardless of file count.
## Non-negotiables
1. NEVER push without pre-commit and pre-push hooks installed.
2. NEVER bypass hooks with `--no-verify` — if a hook fails, fix the root cause.
3. NEVER push without a version bump. Every push = new version.
4. The bump commit MUST use the `chore: adjust package.json version (bump)` message (see `commit-convention`).
5. lint-staged MUST run on every commit. A green lint-staged is a prerequisite for the commit to be created.
6. If `pnpm` is not the package manager, substitute with `npm` or `yarn` but keep the same flow.
## Quick verification checklist
Before declaring the push flow set up, confirm:
- [ ] `lefthook.yml` exists with `pre-commit` and `pre-push` hooks
- [ ] `pnpm exec lefthook install` has been run (hooks registered in `.git/hooks/`)
- [ ] `package.json` has a `lint-staged` block
- [ ] `package.json` has a `bump` script
- [ ] A dry-run commit triggers lint-staged
- [ ] A dry-run push triggers the version bump
@@ -0,0 +1,389 @@
//! Architecture layering audit for clean-architecture compliance.
//!
//! Scans Rust source files in the workspace and reports violations of the
//! dependency rule: domain must not import from outer layers, application
//! must not import from infrastructure or interfaces, etc.
//!
//! # Flow
//!
//! `audit_layering(workspace_dir)` → walk `apps/` → classify crate by path →
//! scan `use` statements → match against forbidden crates → collect violations.
use anyhow::Result;
use ignore::Walk;
use std::path::Path;
use tracing::instrument;
/// A single layering violation found during the audit.
#[derive(Debug, Clone)]
pub struct Violation {
/// Severity level.
pub severity: Severity,
/// Which crate layer caused the violation.
pub layer: &'static str,
/// File path relative to workspace root.
pub file: String,
/// Line number (1-indexed).
pub line: usize,
/// Human-readable description.
pub message: String,
}
/// How severe a violation is.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Severity {
Error,
Warning,
Info,
}
impl std::fmt::Display for Severity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Severity::Error => write!(f, "ERROR"),
Severity::Warning => write!(f, "WARN"),
Severity::Info => write!(f, "INFO"),
}
}
}
/// Result of an architecture audit.
#[derive(Debug, Clone)]
pub struct AuditReport {
/// All violations found, grouped by severity.
pub violations: Vec<Violation>,
/// Number of source files scanned.
pub files_scanned: usize,
}
impl AuditReport {
/// True if any ERROR-level violations exist.
pub fn has_errors(&self) -> bool {
self.violations.iter().any(|v| v.severity == Severity::Error)
}
/// Number of errors.
pub fn error_count(&self) -> usize {
self.violations.iter().filter(|v| v.severity == Severity::Error).count()
}
/// Number of warnings.
pub fn warning_count(&self) -> usize {
self.violations.iter().filter(|v| v.severity == Severity::Warning).count()
}
}
/// Classify a path into a clean-architecture layer name.
fn classify_layer(crate_path: &Path) -> Option<&'static str> {
let path_str = crate_path.to_string_lossy();
if path_str.contains("/domain") || path_str.ends_with("/domain") {
Some("domain")
} else if path_str.contains("/application") || path_str.ends_with("/application") {
Some("application")
} else if path_str.contains("/infrastructure") || path_str.ends_with("/infrastructure") {
Some("infrastructure")
} else if path_str.contains("/interfaces/") {
Some("interfaces")
} else if path_str.contains("/gateway") {
Some("gateway")
} else {
None
}
}
/// Forbidden import patterns per layer.
///
/// Returns a list of crate prefixes that the given layer must NOT import.
fn forbidden_imports(layer: &str) -> &'static [&'static str] {
match layer {
"domain" => &[
"zesdex_application",
"zesdex_infrastructure",
"zesdex_tui",
"zesdex_api",
"zesdex_daemon",
"zesdex_ws",
"zesdex_grpc",
"zesdex_web",
"zesdex_gateway",
"tokio",
"axum",
"reqwest",
"rusqlite",
"ratatui",
"crossterm",
"tower",
"tower_http",
"argon2",
"jsonwebtoken",
"rmcp",
"lsp_types",
"tiktoken_rs",
"syntect",
"pulldown_cmark",
"serde_yaml_ng",
"ignore",
"dom_smoothie",
"fast_html2md",
"scraper",
"clap",
],
"application" => &[
"zesdex_infrastructure",
"zesdex_tui",
"zesdex_api",
"zesdex_daemon",
"zesdex_ws",
"zesdex_grpc",
"zesdex_web",
"zesdex_gateway",
],
"infrastructure" => &[
"zesdex_tui",
"zesdex_api",
"zesdex_daemon",
"zesdex_ws",
"zesdex_grpc",
"zesdex_web",
"zesdex_gateway",
],
_ => &[],
}
}
/// Scan a single Rust source file for forbidden imports.
fn scan_file(
file_path: &Path,
layer: &'static str,
root: &Path,
) -> Vec<Violation> {
let mut violations = Vec::new();
let content = match std::fs::read_to_string(file_path) {
Ok(c) => c,
Err(_) => return violations,
};
let forbidden = forbidden_imports(layer);
if forbidden.is_empty() {
return violations;
}
let relative = file_path
.strip_prefix(root)
.unwrap_or(file_path)
.to_string_lossy()
.to_string();
for (line_num, line) in content.lines().enumerate() {
let trimmed = line.trim();
// Match: `use zesdex_application::...` or `use zesdex_infrastructure::...`
if trimmed.starts_with("use ") {
for &forbidden in forbidden {
let pattern = format!("use {forbidden}");
if trimmed.starts_with(&pattern) || trimmed.starts_with("use crate::") {
// `use crate::` in domain could reference domain-only items — skip.
continue;
}
if trimmed.starts_with(&pattern) || trimmed.starts_with(&format!("use {forbidden}::")) {
// Skip test code — test modules commonly import outer layers.
let is_test = content[..content.len().saturating_sub(1)]
.contains("#[cfg(test)]");
if is_test {
continue;
}
violations.push(Violation {
severity: Severity::Error,
layer,
file: relative.clone(),
line: line_num + 1,
message: format!(
"Layer '{layer}' must not depend on '{forbidden}': {trimmed}"
),
});
}
}
}
}
violations
}
/// Run a full architecture layering audit on the workspace at `root`.
///
/// Walks all `.rs` files under `root/apps/`, classifies each by its parent
/// crate, and checks `use` statements against the dependency rule.
#[instrument(skip(root))]
pub fn audit_layering(root: &Path) -> Result<AuditReport> {
let apps_dir = root.join("apps");
if !apps_dir.is_dir() {
return Ok(AuditReport {
violations: vec![Violation {
severity: Severity::Warning,
layer: "workspace",
file: "apps/".to_string(),
line: 0,
message: format!("apps/ directory not found at {}", apps_dir.display()),
}],
files_scanned: 0,
});
}
let mut violations = Vec::new();
let mut files_scanned = 0;
for entry in Walk::new(&apps_dir).flatten() {
if entry.file_type().is_none_or(|ft| !ft.is_file()) {
continue;
}
let path = entry.path();
if path.extension().is_none_or(|e| e != "rs") {
continue;
}
// Determine which crate this file belongs to by walking up.
let layer = path
.ancestors()
.skip(1)
.find_map(|p| classify_layer(p));
if let Some(layer) = layer {
files_scanned += 1;
violations.extend(scan_file(path, layer, root));
}
}
Ok(AuditReport {
violations,
files_scanned,
})
}
/// Count lines of code and nesting depth in a Rust source file.
pub fn check_function_metrics(content: &str) -> Vec<Violation> {
let mut violations = Vec::new();
let mut in_function = false;
let mut fn_start = 0;
let mut fn_name = String::new();
let mut brace_depth = 0;
let mut max_nesting = 0;
let mut current_nesting: i32 = 0;
for (i, line) in content.lines().enumerate() {
let line_num = i + 1;
// Track function entry.
if line.trim().starts_with("fn ") && line.trim().ends_with('{') {
in_function = true;
fn_start = line_num;
fn_name = line.trim().to_string();
brace_depth = 1;
max_nesting = 0;
current_nesting = 0;
continue;
}
if in_function {
for ch in line.chars() {
match ch {
'{' => {
brace_depth += 1;
current_nesting += 1;
max_nesting = max_nesting.max(current_nesting);
}
'}' => {
brace_depth -= 1;
current_nesting = (current_nesting.saturating_sub(1)).max(0);
if brace_depth == 0 {
// End of function — check metrics.
let fn_lines = line_num - fn_start;
if fn_lines > 40 {
violations.push(Violation {
severity: Severity::Warning,
layer: "code",
file: String::new(),
line: fn_start,
message: format!(
"Function too long: {} lines (max 40): {}",
fn_lines, fn_name
),
});
}
if max_nesting >= 4 {
violations.push(Violation {
severity: Severity::Warning,
layer: "code",
file: String::new(),
line: fn_start,
message: format!(
"Deep nesting (level {}) in: {}",
max_nesting, fn_name
),
});
}
in_function = false;
break;
}
}
_ => {}
}
}
}
}
violations
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn domain_layer_forbidden_imports() {
let forbidden = forbidden_imports("domain");
assert!(forbidden.contains(&"tokio"));
assert!(forbidden.contains(&"zesdex_application"));
assert!(forbidden.contains(&"axum"));
}
#[test]
fn application_layer_forbidden_imports() {
let forbidden = forbidden_imports("application");
assert!(forbidden.contains(&"zesdex_infrastructure"));
assert!(!forbidden.contains(&"tokio")); // tokio is allowed in application
}
#[test]
fn classify_layer_works() {
let p = Path::new("/root/apps/domain/src/lib.rs");
assert_eq!(classify_layer(p), Some("domain"));
let p = Path::new("/root/apps/application/src/lib.rs");
assert_eq!(classify_layer(p), Some("application"));
let p = Path::new("/root/apps/gateway/src/main.rs");
assert_eq!(classify_layer(p), Some("gateway"));
}
#[test]
fn function_metrics_short_function_ok() {
let content = "fn ok() {\n let x = 1;\n}\n";
let violations = check_function_metrics(content);
let long: Vec<_> = violations.iter().filter(|v| v.message.contains("Function too long")).collect();
assert!(long.is_empty(), "short function should not trigger");
}
#[test]
fn function_metrics_reports_long_function() {
let mut lines = String::from("fn long() {\n");
for _ in 0..45 {
lines.push_str(" let _ = 1;\n");
}
lines.push_str("}\n");
let violations = check_function_metrics(&lines);
let long: Vec<_> = violations.iter().filter(|v| v.message.contains("Function too long")).collect();
assert!(!long.is_empty(), "long function should trigger warning");
}
}
@@ -0,0 +1,280 @@
//! Code-quality analysis for clean-code compliance.
//!
//! Scans Rust source files for common clean-code violations:
//! - Missing doc comments on `pub` items
//! - `.unwrap()` / `.expect()` in production (non-test) code
//! - `#[allow(...)]` / `#[expect(...)]` compiler bypasses
//! - Magic number literals (integer/float constants)
//! - Commented-out code blocks
//!
//! # Flow
//!
//! `scan_file(path, root)` → read source → classify lines into scopes →
//! match each rule → return `Vec<Finding>`.
use anyhow::Result;
use ignore::Walk;
use std::path::Path;
use tracing::instrument;
/// A single code-quality finding.
#[derive(Debug, Clone)]
pub struct Finding {
pub severity: super::arch_audit::Severity,
pub rule: &'static str,
pub file: String,
pub line: usize,
pub message: String,
}
/// Result of a code-quality scan.
#[derive(Debug, Clone)]
pub struct CodeQualityReport {
pub findings: Vec<Finding>,
pub files_scanned: usize,
}
impl CodeQualityReport {
pub fn has_errors(&self) -> bool {
self.findings.iter().any(|f| f.severity == super::arch_audit::Severity::Error)
}
pub fn count_by_rule(&self) -> Vec<(&'static str, usize)> {
let mut counts: std::collections::HashMap<&str, usize> = std::collections::HashMap::new();
for f in &self.findings {
*counts.entry(f.rule).or_default() += 1;
}
let mut sorted: Vec<_> = counts.into_iter().collect();
sorted.sort_by_key(|(_, c)| *c);
sorted.reverse();
sorted
}
}
/// Check if a line is inside a `#[cfg(test)]` block.
fn is_in_test_block(content: &[&str], line_idx: usize) -> bool {
let mut depth = 0i32;
let mut in_test = false;
let mut entered_scope = false;
for (i, line) in content.iter().enumerate() {
if i > line_idx {
break;
}
if line.contains("#[cfg(test)]") {
in_test = true;
}
depth += line.matches('{').count() as i32;
if in_test && depth > 0 {
entered_scope = true;
}
depth -= line.matches('}').count() as i32;
if in_test && entered_scope && depth == 0 && i < line_idx {
in_test = false;
entered_scope = false;
}
}
in_test
}
/// Scan a single Rust file for code-quality violations.
pub fn scan_quality_file(file_path: &Path, root: &Path) -> Vec<Finding> {
let mut findings = Vec::new();
let content = match std::fs::read_to_string(file_path) {
Ok(c) => c,
Err(_) => return findings,
};
let relative = file_path
.strip_prefix(root)
.unwrap_or(file_path)
.to_string_lossy()
.to_string();
let lines: Vec<&str> = content.lines().collect();
// Track pub items for doc-comment checking.
let mut prev_line_doc = false;
let mut prev_line_empty = false;
for (i, line) in lines.iter().enumerate() {
let trimmed = line.trim();
let line_num = i + 1;
let in_test = is_in_test_block(&lines, i);
// ── Rule: Compiler bypass ──────────────────────────────────────
if trimmed.starts_with("#[allow(") || trimmed.starts_with("#[expect(") {
// Skip if this is in the workspace lints config (Cargo.toml)
if trimmed.contains("clippy::") || trimmed.contains("dead_code") {
findings.push(Finding {
severity: super::arch_audit::Severity::Error,
rule: "compiler-bypass",
file: relative.clone(),
line: line_num,
message: format!("Compiler bypass attribute found: {trimmed}"),
});
}
}
// ── Rule: unwrap / expect in production code ───────────────────
if !in_test {
if let Some(col) = trimmed.find(".unwrap(") {
// Allow unwrap in test code and in `#[]` attributes
if !trimmed.starts_with("//") {
let snippet = &trimmed[col..(col + 20).min(trimmed.len())];
findings.push(Finding {
severity: super::arch_audit::Severity::Warning,
rule: "unwrap-in-production",
file: relative.clone(),
line: line_num,
message: format!(
"Use `?` or proper error handling instead of `.unwrap()`: {snippet}..."
),
});
}
}
if let Some(col) = trimmed.find(".expect(") {
if !trimmed.starts_with("//") {
let snippet = &trimmed[col..(col + 20).min(trimmed.len())];
findings.push(Finding {
severity: super::arch_audit::Severity::Info,
rule: "expect-in-production",
file: relative.clone(),
line: line_num,
message: format!(".expect() with message: {snippet}..."),
});
}
}
}
// ── Rule: Missing doc comments on pub items ────────────────────
if (trimmed.starts_with("pub ") || trimmed.starts_with("pub("))
&& !prev_line_doc && !prev_line_empty
{
// Check it's a struct/enum/fn/trait/type/const/mod
let is_item = trimmed.starts_with("pub fn ")
|| trimmed.starts_with("pub struct ")
|| trimmed.starts_with("pub enum ")
|| trimmed.starts_with("pub trait ")
|| trimmed.starts_with("pub type ")
|| trimmed.starts_with("pub const ")
|| trimmed.starts_with("pub mod ")
|| trimmed.starts_with("pub(crate) fn ")
|| trimmed.starts_with("pub(crate) struct ")
|| trimmed.starts_with("pub(crate) enum ")
|| trimmed.starts_with("pub(crate) trait ");
if is_item {
findings.push(Finding {
severity: super::arch_audit::Severity::Info,
rule: "missing-doc",
file: relative.clone(),
line: line_num,
message: format!("Missing doc comment on pub item: {trimmed}"),
});
}
}
// ── Rule: Commented-out code ───────────────────────────────────
if trimmed.starts_with("// ") && !in_test {
let stripped = trimmed.trim_start_matches("// ");
if stripped.starts_with("fn ")
|| stripped.starts_with("let ")
|| stripped.starts_with("if ")
|| stripped.starts_with("for ")
|| stripped.starts_with("while ")
|| stripped.starts_with("match ")
|| stripped.starts_with("pub ")
|| stripped.starts_with("impl ")
{
findings.push(Finding {
severity: super::arch_audit::Severity::Info,
rule: "commented-code",
file: relative.clone(),
line: line_num,
message: format!("Commented-out code detected: {trimmed}"),
});
}
}
// Track doc-comment state
prev_line_doc = trimmed.starts_with("///") || trimmed.starts_with("//!");
prev_line_empty = trimmed.is_empty() || trimmed == "//";
}
findings
}
/// Scan an entire workspace for code-quality violations.
#[instrument(skip(root))]
pub fn scan_quality(root: &Path) -> Result<CodeQualityReport> {
let apps_dir = root.join("apps");
if !apps_dir.is_dir() {
return Ok(CodeQualityReport {
findings: vec![],
files_scanned: 0,
});
}
let mut findings = Vec::new();
let mut files_scanned = 0;
for entry in Walk::new(&apps_dir).flatten() {
if entry.file_type().is_none_or(|ft| !ft.is_file()) {
continue;
}
let path = entry.path();
if path.extension().is_none_or(|e| e != "rs") {
continue;
}
files_scanned += 1;
findings.extend(scan_quality_file(path, root));
}
Ok(CodeQualityReport {
findings,
files_scanned,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detects_unwrap_in_production_code() {
let dir = std::env::temp_dir().join(format!("qtest_{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&dir).unwrap();
let file = dir.join("test.rs");
std::fs::write(&file, "fn x() { let y = foo.unwrap(); }\n").unwrap();
let findings = scan_quality_file(&file, &dir);
let unwrap_findings: Vec<_> = findings.iter().filter(|f| f.rule == "unwrap-in-production").collect();
assert!(!unwrap_findings.is_empty(), "should detect unwrap");
}
#[test]
fn skips_unwrap_in_test_block() {
let dir = std::env::temp_dir().join(format!("qtest_{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&dir).unwrap();
let file = dir.join("test.rs");
std::fs::write(
&file,
"#[cfg(test)]\nmod tests {\n fn x() { let y = foo.unwrap(); }\n}\n",
)
.unwrap();
let findings = scan_quality_file(&file, &dir);
let unwrap_findings: Vec<_> = findings.iter().filter(|f| f.rule == "unwrap-in-production").collect();
assert!(unwrap_findings.is_empty(), "should skip unwrap in test blocks");
}
#[test]
fn detects_allow_attributes() {
let dir = std::env::temp_dir().join(format!("qtest_{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&dir).unwrap();
let file = dir.join("test.rs");
std::fs::write(&file, "#[allow(clippy::too_many_arguments)]\nfn x() {}\n").unwrap();
let findings = scan_quality_file(&file, &dir);
let bypass_findings: Vec<_> = findings.iter().filter(|f| f.rule == "compiler-bypass").collect();
assert!(!bypass_findings.is_empty(), "should detect allow attributes");
}
}
@@ -0,0 +1,254 @@
//! Commit message validation following Conventional Commits (Bahasa Indonesia).
//!
//! # Flow
//!
//! `validate_commit_message(msg)` → parse subject → check type, scope,
//! description format → return list of errors (empty = valid).
//!
//! # Format
//!
//! ```text
//! feat(scope): description
//! fix(scope): description
//! chore: description
//! docs: description
//! refactor: description
//! test: description
//! style: description
//! perf: description
//! ci: description
//! ```
use regex::Regex;
/// Valid commit types.
const VALID_TYPES: &[&str] = &[
"feat", "fix", "chore", "docs", "refactor", "test", "style", "perf", "ci", "build", "revert",
];
/// Validate a commit message against the Conventional Commits spec.
///
/// Returns `Ok(())` if valid, or `Err(errors)` with human-readable messages.
pub fn validate_commit_message(msg: &str) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
let msg = msg.trim();
if msg.is_empty() {
errors.push("Commit message must not be empty.".to_string());
return Err(errors);
}
// Split subject and body.
let subject = msg.lines().next().unwrap_or(msg);
// Check subject length.
if subject.len() > 72 {
errors.push(format!(
"Subject line is {} characters (max 72).",
subject.len()
));
}
// Parse: `type(scope): description` or `type!: description` or `type: description`
let re = Regex::new(
r"^(?P<type>[a-z]+)(?:\((?P<scope>[^)]+)\))?(?P<breaking>!)?:\s+(?P<desc>.+)$",
)
.expect("valid regex for commit parsing");
match re.captures(subject) {
None => {
errors.push(format!(
"Subject does not match Conventional Commits format.\n\
Expected: `type(scope): description`\n\
Got: {subject}\n\
Valid types: {}",
VALID_TYPES.join(", ")
));
}
Some(caps) => {
let type_ = caps.name("type").map(|m| m.as_str()).unwrap_or("");
let scope = caps.name("scope").map(|m| m.as_str());
let desc = caps.name("desc").map(|m| m.as_str()).unwrap_or("");
// Validate type.
if !VALID_TYPES.contains(&type_) {
errors.push(format!(
"Invalid commit type '{type_}'. Valid types: {}",
VALID_TYPES.join(", ")
));
}
// Scope is lowercase, no spaces.
if let Some(s) = scope {
if s.contains(' ') {
errors.push(format!("Scope must not contain spaces: '{s}'"));
}
if s.chars().any(|c| c.is_uppercase()) {
errors.push(format!("Scope must be lowercase: '{s}'"));
}
}
// Description rules.
if desc.is_empty() {
errors.push("Description must not be empty.".to_string());
} else {
let first_char = desc.chars().next().unwrap_or(' ');
if first_char.is_uppercase() {
errors.push(format!(
"Description must start with lowercase: '{desc}'"
));
}
if desc.ends_with('.') {
errors.push(format!(
"Description must not end with a period: '{desc}'"
));
}
}
// Type-specific rules.
match type_ {
"chore" | "docs" | "refactor" | "test" | "style" | "perf" | "ci" | "build"
| "revert" if scope.is_some() => {
errors.push(format!(
"'{type_}' commits should not use a scope. \
Only 'feat' and 'fix' require scopes."
));
}
_ => {}
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
/// Convert a commit message to a structured representation.
pub struct CommitInfo {
pub type_: String,
pub scope: Option<String>,
pub breaking: bool,
pub description: String,
pub body: Option<String>,
}
/// Parse a commit message into its structured components.
pub fn parse_commit_message(msg: &str) -> Option<CommitInfo> {
let msg = msg.trim();
let subject = msg.lines().next()?;
let re = Regex::new(
r"^(?P<type>[a-z]+)(?:\((?P<scope>[^)]+)\))?(?P<breaking>!)?:\s+(?P<desc>.+)$",
)
.expect("valid regex");
let caps = re.captures(subject)?;
let body_lines: Vec<&str> = msg.lines().skip(1).collect();
let body = if body_lines.is_empty() {
None
} else {
Some(body_lines.join("\n"))
};
Some(CommitInfo {
type_: caps.name("type").map(|m| m.as_str()).unwrap_or("").to_string(),
scope: caps.name("scope").map(|m| m.as_str().to_string()),
breaking: caps.name("breaking").is_some(),
description: caps.name("desc").map(|m| m.as_str()).unwrap_or("").to_string(),
body,
})
}
/// Suggest a commit message template for a given change type.
pub fn suggest_template(type_: &str, scope: Option<&str>) -> String {
match type_ {
"feat" | "fix" if scope.is_some() => {
format!("{}({}): <imperative description>", type_, scope.unwrap())
}
"feat" | "fix" => {
format!("{}(<scope>): <imperative description>", type_)
}
_ => {
format!("{}: <imperative description>", type_)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_feat_commit() {
assert!(validate_commit_message("feat(tool): add batch file delete").is_ok());
}
#[test]
fn valid_fix_commit() {
assert!(validate_commit_message("fix(ipc): reconnect loop on socket timeout").is_ok());
}
#[test]
fn valid_chore_commit() {
assert!(validate_commit_message("chore: bump reqwest to 0.13").is_ok());
}
#[test]
fn valid_docs_commit() {
assert!(validate_commit_message("docs: add architecture diagram to README").is_ok());
}
#[test]
fn rejects_empty_message() {
assert!(validate_commit_message("").is_err());
}
#[test]
fn rejects_missing_colon() {
assert!(validate_commit_message("feat:missing space").is_err());
}
#[test]
fn rejects_uppercase_description() {
let result = validate_commit_message("feat(tool): Add new feature");
assert!(result.is_err(), "should reject uppercase start");
}
#[test]
fn rejects_trailing_period() {
let result = validate_commit_message("feat(tool): add new feature.");
assert!(result.is_err(), "should reject trailing period");
}
#[test]
fn rejects_invalid_type() {
let result = validate_commit_message("wat(tool): something broke");
assert!(result.is_err(), "should reject invalid type");
}
#[test]
fn parses_valid_commit() {
let parsed = parse_commit_message("feat(agent): add parallel execution\n\nWith cycle orchestration.").unwrap();
assert_eq!(parsed.type_, "feat");
assert_eq!(parsed.scope, Some("agent".to_string()));
assert!(!parsed.breaking);
assert_eq!(parsed.description, "add parallel execution");
assert!(parsed.body.unwrap().contains("cycle orchestration"));
}
#[test]
fn parses_breaking_change() {
let parsed = parse_commit_message("feat(api)!: change response format").unwrap();
assert!(parsed.breaking);
}
#[test]
fn suggests_template() {
let tpl = suggest_template("feat", Some("tool"));
assert_eq!(tpl, "feat(tool): <imperative description>");
}
}

Some files were not shown because too many files have changed in this diff Show More