# Plan: Refactor tools.c into Individual Tool Files ## Problem `src/tools.c` is ~6,123 lines containing 44 `execute_*` functions, a ~650-line schema builder, shared helpers, and a dispatcher. It is the largest file in the project and difficult to navigate, review, and maintain. ## Goal Split `src/tools.c` into focused files organized by tool group, with shared helpers in a common module. The public API (`tools.h`) remains unchanged — no callers need modification. ## Target File Structure ``` src/ ├── tools.h # Public API (UNCHANGED) ├── tools_internal.h # Shared internal helpers + per-tool-file function declarations ├── tools_common.c # Shared helper implementations ├── tools_schema.c # tools_build_openai_schema_json() ├── tools_dispatch.c # tools_init, tools_cleanup, tools_execute dispatcher └── tools/ ├── tool_nostr_post.c # nostr_post, nostr_post_readme, nostr_file_md_to_longform_post ├── tool_nostr_query.c # nostr_query ├── tool_nostr_identity.c # nostr_pubkey, nostr_npub, nostr_encode, nostr_decode ├── tool_nostr_social.c # nostr_react, nostr_profile_get, nostr_nip05_lookup ├── tool_nostr_dm.c # nostr_dm_send, nostr_dm_send_nip17, nostr_encrypt, nostr_decrypt ├── tool_nostr_relay.c # nostr_relay_status, nostr_relay_info ├── tool_nostr_list.c # nostr_list_manage, nostr_delete ├── tool_skill.c # skill_create, skill_list, skill_adopt, skill_remove, skill_edit, skill_search ├── tool_task.c # task_list, task_manage ├── tool_local.c # local_shell_exec, local_file_read, local_file_write, local_http_fetch ├── tool_admin.c # admin_identity, nostr_admin_profile, nostr_admin_contacts, nostr_admin_relays, nostr_admin_notes ├── tool_agent.c # agent_identity, agent_version, message_current ├── tool_model.c # model_get, model_set, model_list └── tool_meta.c # tool_list, trigger_list ``` ## Shared Helpers → tools_common.c These helpers are used by multiple tool groups and must be non-static in `tools_common.c`, declared in `tools_internal.h`: | Helper | Current lines | Used by | |---|---|---| | `json_error()` | 27-35 | ALL tools | | `repair_json_control_chars()` | 37-101 | nostr_post, nostr_post_readme | | `find_key_start()` | 103-111 | nostr_post loose parser | | `skip_ws()` | 113-116 | parse_tool_args_json, loose parser | | `parse_loose_kind()` | 118-135 | nostr_post | | `parse_loose_json_string_value()` | 137-186 | nostr_post | | `parse_loose_nostr_post_args()` | 188-212 | nostr_post | | `ensure_tags_array()` | 214-227 | nostr_post | | `has_tag_key()` | 229-243 | longform post helpers | | `add_string_tag()` | 245-255 | nostr_post, delete, react, skill, longform | | `remove_tag_key_all()` | 257-270 | upsert_string_tag | | `upsert_string_tag()` | 272-276 | skill_edit | | `parse_tool_args_json()` | 590-564 | ALL tools | | `is_hex_string_len()` | 581-588 | nostr tools, skill tools, DM tools | | `find_tag_value_string()` | 855-872 | skill tools, list_manage, longform | | `tag_tuple_equal()` | 813-828 | skill tools, list_manage | | `tags_contains_tuple()` | 830-840 | skill tools, list_manage | | `remove_matching_tag_tuples()` | 842-853 | skill tools, list_manage | | `build_tool_path()` | (used by local tools + readme) | local tools, readme | ## Tool-Local Helpers (stay static in their tool file) | Helper | Destination | |---|---| | `validate_skill_d_tag()` | `tool_skill.c` | | `fetch_adoption_list_tags()` | `tool_skill.c` | | `publish_adoption_list()` | `tool_skill.c` | | `adoption_tags_contains_address()` | `tool_skill.c` | | `extract_skill_summary()` | `tool_skill.c` | | `ci_contains()` | `tool_skill.c` | | `d_tagify_string()` | `tool_nostr_post.c` | | `first_markdown_h1()` | `tool_nostr_post.c` | | `first_markdown_paragraph()` | `tool_nostr_post.c` | | `trim_copy()` | `tool_nostr_post.c` | | `auto_enrich_longform_tags()` | `tool_nostr_post.c` | | `basename_lowercase_dup()` | `tool_nostr_post.c` | | `detect_ca_bundle_path_for_tools()` | `tool_local.c` | | `local_http_fetch_write_cb()` | `tool_local.c` | | `persist_llm_config()` | `tool_model.c` | ## Tool Function Signatures Each tool file exposes its `execute_*` functions via `tools_internal.h`. Two signature patterns exist: ```c // Pattern A: context-free (only needs args) char* execute_nostr_post(const char* args_json); // Pattern B: context-dependent char* execute_skill_create(tools_context_t* ctx, const char* args_json); ``` ## tools_internal.h Structure ```c #ifndef DIDACTYL_TOOLS_INTERNAL_H #define DIDACTYL_TOOLS_INTERNAL_H #include "tools.h" #include "cjson/cJSON.h" // ─── Shared helpers (tools_common.c) ─── char* json_error(const char* msg); cJSON* parse_tool_args_json(const char* args_json); int is_hex_string_len(const char* s, size_t expected_len); int add_string_tag(cJSON* tags, const char* key, const char* value); int remove_tag_key_all(cJSON* tags, const char* key); int upsert_string_tag(cJSON* tags, const char* key, const char* value); int has_tag_key(cJSON* tags, const char* key); cJSON* ensure_tags_array(cJSON** tags_inout); cJSON* find_tag_value_string(cJSON* tags, const char* key); int tag_tuple_equal(cJSON* a, cJSON* b); int tags_contains_tuple(cJSON* tags, cJSON* tuple); int remove_matching_tag_tuples(cJSON* tags, cJSON* tuple); char* repair_json_control_chars(const char* in); cJSON* parse_loose_nostr_post_args(const char* in); int build_tool_path(tools_context_t* ctx, const char* rel, char* out, size_t out_size); // ─── Per-tool-file execute functions ─── // tool_nostr_post.c char* execute_nostr_post(const char* args_json); char* execute_nostr_post_readme(tools_context_t* ctx, const char* args_json); char* execute_nostr_file_md_to_longform_post(tools_context_t* ctx, const char* args_json); // tool_nostr_query.c char* execute_nostr_query(const char* args_json); // tool_nostr_identity.c char* execute_nostr_pubkey(tools_context_t* ctx, const char* args_json); char* execute_nostr_npub(tools_context_t* ctx, const char* args_json); char* execute_nostr_encode(const char* args_json); char* execute_nostr_decode(const char* args_json); // tool_nostr_social.c char* execute_nostr_react(const char* args_json); char* execute_nostr_profile_get(const char* args_json); char* execute_nostr_nip05_lookup(const char* args_json); // tool_nostr_dm.c char* execute_nostr_dm_send(const char* args_json); char* execute_nostr_dm_send_nip17(const char* args_json); char* execute_nostr_encrypt(tools_context_t* ctx, const char* args_json); char* execute_nostr_decrypt(tools_context_t* ctx, const char* args_json); // tool_nostr_relay.c char* execute_nostr_relay_status(const char* args_json); char* execute_nostr_relay_info(const char* args_json); // tool_nostr_list.c char* execute_nostr_list_manage(tools_context_t* ctx, const char* args_json); char* execute_nostr_delete(const char* args_json); // tool_skill.c char* execute_skill_create(tools_context_t* ctx, const char* args_json); char* execute_skill_list(tools_context_t* ctx, const char* args_json); char* execute_skill_adopt(tools_context_t* ctx, const char* args_json); char* execute_skill_remove(tools_context_t* ctx, const char* args_json); char* execute_skill_edit(tools_context_t* ctx, const char* args_json); char* execute_skill_search(const char* args_json); // tool_task.c char* execute_task_list(tools_context_t* ctx, const char* args_json); char* execute_task_manage(tools_context_t* ctx, const char* args_json); // tool_local.c char* execute_local_http_fetch(tools_context_t* ctx, const char* args_json); char* execute_local_shell_exec(tools_context_t* ctx, const char* args_json); char* execute_local_file_read(tools_context_t* ctx, const char* args_json); char* execute_local_file_write(tools_context_t* ctx, const char* args_json); // tool_admin.c char* execute_admin_identity(tools_context_t* ctx, const char* args_json); char* execute_nostr_admin_profile(tools_context_t* ctx, const char* args_json); char* execute_nostr_admin_contacts(tools_context_t* ctx, const char* args_json); char* execute_nostr_admin_relays(tools_context_t* ctx, const char* args_json); char* execute_nostr_admin_notes(tools_context_t* ctx, const char* args_json); // tool_agent.c char* execute_agent_identity(tools_context_t* ctx, const char* args_json); char* execute_agent_version(const char* args_json); char* execute_message_current(tools_context_t* ctx, const char* args_json); // tool_model.c char* execute_model_get(const char* args_json); char* execute_model_set(tools_context_t* ctx, const char* args_json); char* execute_model_list(const char* args_json); // tool_meta.c char* execute_tool_list(tools_context_t* ctx, const char* args_json); char* execute_trigger_list(tools_context_t* ctx, const char* args_json); #endif ``` ## Makefile Changes Replace the single `$(SRC_DIR)/tools.c` entry with: ```makefile SRCS = \ $(SRC_DIR)/main.c \ $(SRC_DIR)/config.c \ $(SRC_DIR)/context.c \ $(SRC_DIR)/llm.c \ $(SRC_DIR)/nostr_handler.c \ $(SRC_DIR)/agent.c \ $(SRC_DIR)/tools_common.c \ $(SRC_DIR)/tools_schema.c \ $(SRC_DIR)/tools_dispatch.c \ $(SRC_DIR)/tools/tool_nostr_post.c \ $(SRC_DIR)/tools/tool_nostr_query.c \ $(SRC_DIR)/tools/tool_nostr_identity.c \ $(SRC_DIR)/tools/tool_nostr_social.c \ $(SRC_DIR)/tools/tool_nostr_dm.c \ $(SRC_DIR)/tools/tool_nostr_relay.c \ $(SRC_DIR)/tools/tool_nostr_list.c \ $(SRC_DIR)/tools/tool_skill.c \ $(SRC_DIR)/tools/tool_task.c \ $(SRC_DIR)/tools/tool_local.c \ $(SRC_DIR)/tools/tool_admin.c \ $(SRC_DIR)/tools/tool_agent.c \ $(SRC_DIR)/tools/tool_model.c \ $(SRC_DIR)/tools/tool_meta.c \ $(SRC_DIR)/trigger_manager.c \ $(SRC_DIR)/prompt_template.c \ $(SRC_DIR)/http_api.c \ $(SRC_DIR)/mongoose.c \ $(SRC_DIR)/debug.c ``` ## Implementation Steps ### Phase 1: Create shared infrastructure - [ ] Create `src/tools_internal.h` with shared helper declarations and all `execute_*` prototypes - [ ] Create `src/tools_common.c` — move shared helpers from `tools.c`, remove `static`, add `#include "tools_internal.h"` - [ ] Create `src/tools_schema.c` — move `tools_build_openai_schema_json()` from `tools.c` - [ ] Create `src/tools_dispatch.c` — move `tools_init()`, `tools_cleanup()`, `tools_execute()` from `tools.c` - [ ] Update `Makefile` SRCS to include new files (keep `tools.c` temporarily with remaining functions) - [ ] Build and verify — all tests pass, binary runs ### Phase 2: Extract tool files one at a time Each step: move `execute_*` functions + their local helpers into the new file, remove from `tools.c`, build, verify. - [ ] Create `src/tools/` directory - [ ] Extract `src/tools/tool_nostr_post.c` — `execute_nostr_post`, `execute_nostr_post_readme`, `execute_nostr_file_md_to_longform_post` + local helpers: `d_tagify_string`, `first_markdown_h1`, `first_markdown_paragraph`, `trim_copy`, `auto_enrich_longform_tags`, `basename_lowercase_dup` - [ ] Extract `src/tools/tool_nostr_query.c` — `execute_nostr_query` - [ ] Extract `src/tools/tool_nostr_identity.c` — `execute_nostr_pubkey`, `execute_nostr_npub`, `execute_nostr_encode`, `execute_nostr_decode` - [ ] Extract `src/tools/tool_nostr_social.c` — `execute_nostr_react`, `execute_nostr_profile_get`, `execute_nostr_nip05_lookup` - [ ] Extract `src/tools/tool_nostr_dm.c` — `execute_nostr_dm_send`, `execute_nostr_dm_send_nip17`, `execute_nostr_encrypt`, `execute_nostr_decrypt` - [ ] Extract `src/tools/tool_nostr_relay.c` — `execute_nostr_relay_status`, `execute_nostr_relay_info` - [ ] Extract `src/tools/tool_nostr_list.c` — `execute_nostr_list_manage`, `execute_nostr_delete` - [ ] Extract `src/tools/tool_skill.c` — `execute_skill_create`, `execute_skill_list`, `execute_skill_adopt`, `execute_skill_remove`, `execute_skill_edit`, `execute_skill_search` + local helpers: `validate_skill_d_tag`, `ci_contains`, `fetch_adoption_list_tags`, `publish_adoption_list`, `adoption_tags_contains_address`, `extract_skill_summary` - [ ] Extract `src/tools/tool_task.c` — `execute_task_list`, `execute_task_manage` - [ ] Extract `src/tools/tool_local.c` — `execute_local_http_fetch`, `execute_local_shell_exec`, `execute_local_file_read`, `execute_local_file_write` + local helpers: `detect_ca_bundle_path_for_tools`, `local_http_fetch_write_cb`, `free_string_array_heap` - [ ] Extract `src/tools/tool_admin.c` — `execute_admin_identity`, `execute_nostr_admin_profile`, `execute_nostr_admin_contacts`, `execute_nostr_admin_relays`, `execute_nostr_admin_notes` - [ ] Extract `src/tools/tool_agent.c` — `execute_agent_identity`, `execute_agent_version`, `execute_message_current` - [ ] Extract `src/tools/tool_model.c` — `execute_model_get`, `execute_model_set`, `execute_model_list` + local helper: `persist_llm_config` - [ ] Extract `src/tools/tool_meta.c` — `execute_tool_list`, `execute_trigger_list` ### Phase 3: Cleanup - [ ] Delete `src/tools.c` (now empty — all code moved) - [ ] Remove `src/tools.c` from Makefile SRCS - [ ] Final build + full test run - [ ] Verify binary size is comparable (no accidental code duplication) - [ ] Increment version and push ## Validation Criteria After each extraction step: 1. `make -j4` compiles with zero warnings 2. `./didactyl --debug 5` starts and runs normally 3. Tool execution via DM or `--test-tool` produces identical results ## Risk Mitigation - **Build after every file extraction** — catch issues immediately - **No logic changes** — purely mechanical code movement - **Keep `tools.h` unchanged** — zero impact on callers (`agent.c`, `http_api.c`, `main.c`) - **Git commit after each phase** — easy rollback points