From 1fb2940ba870a109ff64680937a8df6df5d43cb3d0e5ceb200a72234b3168024 Mon Sep 17 00:00:00 2001 From: "jabber.developer" Date: Thu, 30 Apr 2026 18:21:04 +0000 Subject: [PATCH] fix skills --- skills/command-system.md | 304 ++++++++++++++++++++ skills/cproof-structure.md | 129 +++++++++ skills/writing-prs-issues-commits.md | 414 +++++++++++++++++++++++++++ 3 files changed, 847 insertions(+) create mode 100644 skills/command-system.md create mode 100644 skills/cproof-structure.md create mode 100644 skills/writing-prs-issues-commits.md diff --git a/skills/command-system.md b/skills/command-system.md new file mode 100644 index 0000000..fb27087 --- /dev/null +++ b/skills/command-system.md @@ -0,0 +1,304 @@ +--- +name: command-system +description: Use when writing, modifying, or debugging CProof command functions (cmd_funcs.c), command definitions (cmd_defs.c), or autocomplete (cmd_ac.c). Documents the command dispatch architecture, critical argument indexing rules (args[0] contains subcommand name), and common patterns. +--- + +# CProof Command System + +## When to Use This Skill + +Use this skill when: +- Writing new command handler functions in `cmd_funcs.c` +- Adding/modifying command definitions in `cmd_defs.c` +- Implementing autocomplete for commands in `cmd_ac.c` +- Debugging command argument parsing issues + +## Core Architecture + +### Command Structure ([`Command`](src/command/cmd_defs.h:64)) + +```c +typedef struct cmd_t { + gchar* cmd; // e.g., "/ai" + gchar** (*parser)(const char*, int, int, gboolean*); // parse_args, parse_args_with_freetext, parse_args_as_one + int min_args; int max_args; + void (*setting_func)(void); // Shows current settings in console (or NULL) + struct { const char* cmd; gboolean (*func)(ProfWin*, const char*, gchar**); } sub_funcs[50]; + gboolean (*func)(ProfWin*, const char*, gchar**); // Main handler (or NULL) + CommandHelp help; // Tags, synopsis, desc, args, examples +} Command; +``` + +### Help Structure ([`CommandHelp`](src/command/cmd_funcs.h:43)) + +```c +typedef struct cmd_help_t { + gchar* tags[20]; // Command categories (NULL-terminated) + gchar* synopsis[50]; // Usage examples (NULL-terminated) + gchar* desc; // Description text + gchar* args[128][2]; // Argument names and descriptions ({NULL,NULL}-terminated) + gchar* examples[20]; // Example commands (NULL-terminated) +} CommandHelp; +``` + +### Available Tags ([`cmd_defs.c:82-89`](src/command/cmd_defs.c:82)) + +| Tag | Description | +|-----|-------------| +| `CMD_TAG_CHAT` | Chat commands | +| `CMD_TAG_GROUPCHAT` | Group chat commands | +| `CMD_TAG_ROSTER` | Roster management | +| `CMD_TAG_PRESENCE` | Presence commands | +| `CMD_TAG_CONNECTION` | Connection management | +| `CMD_TAG_DISCOVERY` | Discovery commands | +| `CMD_TAG_UI` | UI display commands | +| `CMD_TAG_PLUGINS` | Plugin commands | + +### Dispatch Flow ([`_cmd_execute()`](src/command/cmd_funcs.c:8261)) + +``` +User: /ai set provider openai https://api.openai.com + | + v +parse_args("set provider openai https://...", 0, 5) → {"set","provider","openai","https://...",NULL} + | + v +args[0]="set" matches cmd->sub_funcs[0].cmd="set" + | + v +cmd_ai_set(window, "/ai", args) ← args[0] is "set", NOT "provider"! +``` + +**CRITICAL: Subcommand functions receive the FULL args array. args[0] contains the subcommand name itself. All parameter accesses must be shifted +1.** + +## Argument Indexing Reference + +| User Input | args[0] | args[1] | args[2] | args[3] | +|------------|---------|---------|---------|---------| +| `/ai` | NULL | - | - | - | +| `/ai set provider openai url` | `"set"` | `"provider"` | `"openai"` | `"url"` | +| `/ai set token openai sk-xxx` | `"set"` | `"token"` | `"openai"` | `"sk-xxx"` | +| `/ai start openai/gpt-4o` | `"start"` | `"openai/gpt-4o"` | - | - | +| `/ai remove provider foo` | `"remove"` | `"provider"` | `"foo"` | - | +| `/tls trust` | `"trust"` | - | - | - | +| `/connect account server foo` | `"account"` | `"server"` | `"foo"` | - | + +## Correct Subcommand Function Pattern + +```c +gboolean cmd_ai_set(ProfWin* window, const char* const command, gchar** args) +{ + // args[0] = "set" (subcommand name) + // args[1] = next subcommand (provider/token/org) + + if (args[1] == NULL) { cons_bad_cmd_usage(command); return TRUE; } + + if (g_strcmp0(args[1], "provider") == 0) { + // args[2] = name, args[3] = url + if (g_strv_length(args) < 4) { cons_bad_cmd_usage(command); return TRUE; } + ai_add_provider(args[2], args[3], NULL); + cons_show("Provider '%s' configured with URL: %s", args[2], args[3]); + } else if (g_strcmp0(args[1], "token") == 0) { + // args[2] = provider, args[3] = token + if (g_strv_length(args) < 4) { cons_bad_cmd_usage(command); return TRUE; } + ai_set_provider_key(args[2], args[3]); + } else if (g_strcmp0(args[1], "org") == 0) { + // args[2] = provider, args[3] = org_id + if (g_strv_length(args) < 4) { cons_bad_cmd_usage(command); return TRUE; } + AIProvider* p = ai_get_provider(args[2]); + if (p) { g_free(p->org_id); p->org_id = g_strdup(args[3]); } + } else { + cons_bad_cmd_usage(command); return TRUE; + } + cons_show(""); + return TRUE; +} +``` + +### Common Bug (WRONG) +```c +// BUG: args[0] is "set", not "provider" +if (g_strcmp0(args[0], "provider") == 0) { ... } +// BUG: args[1] is "provider", not the name +ai_add_provider(args[1], args[2], NULL); +``` + +## Command Definition Pattern ([`cmd_defs.c`](src/command/cmd_defs.c)) + +### Macros ([`cmd_defs.c:91-98`](src/command/cmd_defs.c:91)) + +```c +#define CMD_PREAMBLE(c, p, min, max, set) .cmd = c, .parser = p, .min_args = min, .max_args = max, .setting_func = set, +#define CMD_MAINFUNC(f) .func = f, +#define CMD_SUBFUNCS(...) .sub_funcs = { __VA_ARGS__, { NULL, NULL } }, +#define CMD_TAGS(...) .help.tags = { __VA_ARGS__, NULL }, +#define CMD_SYN(...) .help.synopsis = { __VA_ARGS__, NULL }, +#define CMD_DESC(d) .help.desc = d, +#define CMD_ARGS(...) .help.args = { __VA_ARGS__, { NULL, NULL } }, +#define CMD_EXAMPLES(...) .help.examples = { __VA_ARGS__, NULL } +``` + +### Example Definition + +```c +{ CMD_PREAMBLE("/ai", parse_args, 0, 5, NULL) + CMD_SUBFUNCS({ "set", cmd_ai_set }, { "remove", cmd_ai_remove }, + { "start", cmd_ai_start }, { "clear", cmd_ai_clear }, + { "correct", cmd_ai_correct }, { "providers", cmd_ai_providers }) + CMD_MAINFUNC(cmd_ai) + CMD_TAGS(CMD_TAG_CHAT) + CMD_SYN("/ai", "/ai set provider ", "/ai set token ") + CMD_DESC("Interact with AI models via OpenAI-compatible APIs.") + CMD_ARGS({ "", "Display current AI settings" }, + { "set provider ", "Add or update a provider" }) + CMD_EXAMPLES("/ai", "/ai set token openai sk-xxx", "/ai start openai/gpt-4o") +}, +``` + +## Autocomplete Pattern ([`cmd_ac.c`](src/command/cmd_ac.c)) + +### Initialization +```c +// In cmd_ac_init(): +autocomplete_add(ai_subcommands_ac, "set"); +autocomplete_add(ai_subcommands_ac, "remove"); +autocomplete_add(ai_set_subcommands_ac, "provider"); +autocomplete_add(ai_set_subcommands_ac, "token"); +autocomplete_add(ai_set_subcommands_ac, "org"); +g_hash_table_insert(ac_funcs, "/ai", _ai_autocomplete); +``` + +### Callback +```c +static char* _ai_autocomplete(ProfWin* window, const char* const input, gboolean previous) +{ + char* result = NULL; + result = autocomplete_param_with_ac(input, "/ai", ai_subcommands_ac, TRUE, previous); + if (result) return result; + result = autocomplete_param_with_ac(input, "/ai set", ai_set_subcommands_ac, TRUE, previous); + if (result) return result; + // Dynamic: provider names from ai_list_providers() + return NULL; +} +``` + +## Common Patterns + +### Window Type Access +```c +ProfAiWin* aiwin = (window && window->type == WIN_AI) ? (ProfAiWin*)window : wins_get_ai(); +// Or switch: +switch (window->type) { +case WIN_CHAT: { ProfChatWin* w = (ProfChatWin*)window; /* w->barejid */ break; } +case WIN_MUC: { ProfMucWin* w = (ProfMucWin*)window; /* w->roomjid */ break; } +} +``` + +### Connection Check +```c +if (connection_get_status() != JABBER_CONNECTED) { + cons_show("You are not currently connected."); + return TRUE; +} +``` + +### Options Parsing +```c +gchar* opt_keys[] = { "server", "port", "tls", NULL }; +GHashTable* opts = parse_options(&args[args[0] ? 1 : 0], opt_keys, &parsed); +// &args[args[0] ? 1 : 0] skips args[0] if present +``` + +### Memory Management +```c +auto_gcharv gchar** args = cmd->parser(inp, min, max, &result); // Auto-freed +auto_gchar gchar* path = prefs_get_string(PREF_SOME_PREF); // Auto-freed +``` + +## Argument Parsers ([`parser.c`](src/tools/parser.c)) + +| Function | Description | +|----------|-------------| +| `parse_args()` | Standard argument parsing, splits on spaces | +| `parse_args_with_freetext()` | Last argument captures everything after `max` tokens | +| `parse_args_as_one()` | Everything after first space becomes single argument | + +## Console Output Functions ([`ui.h`](src/ui/ui.h)) + +| Function | Description | +|----------|-------------| +| `cons_show(const char* msg, ...)` | Print formatted message to console | +| `cons_show_error(const char* cmd, ...)` | Print error message | +| `cons_bad_cmd_usage(const char* cmd)` | Print usage error | + +## Adding a New Command: Step-by-Step + +### Step 1: Declare Function in `cmd_funcs.h` + +```c +gboolean cmd_mycommand(ProfWin* window, const char* const command, gchar** args); +``` + +### Step 2: Implement Function in `cmd_funcs.c` + +```c +gboolean +cmd_mycommand(ProfWin* window, const char* const command, gchar** args) +{ + // args[0] = first argument (or NULL if none) + if (args[0] == NULL) { + cons_show("No argument provided."); + return TRUE; + } + cons_show("Got: %s", args[0]); + return TRUE; +} +``` + +### Step 3: Add Definition to `command_defs[]` in `cmd_defs.c` + +Find the end of the array (look for `};` after the last entry) and add: + +```c + { CMD_PREAMBLE("/mycommand", + parse_args, 0, 1, NULL) + CMD_MAINFUNC(cmd_mycommand) + CMD_TAGS(CMD_TAG_CHAT) + CMD_SYN("/mycommand []") + CMD_DESC("My new command description.") + CMD_ARGS({ "", "An argument" }) + CMD_EXAMPLES("/mycommand hello") + }, +``` + +### Step 4: Add Autocomplete + +In `cmd_ac.c`: + +1. Add static function declaration at top: +```c +static char* _mycommand_autocomplete(ProfWin* window, const char* const input, gboolean previous); +``` + +2. Register in `cmd_ac_init()`: +```c +g_hash_table_insert(ac_funcs, "/mycommand", _mycommand_autocomplete); +``` + +3. Implement the function: +```c +static char* +_mycommand_autocomplete(ProfWin* window, const char* const input, gboolean previous) +{ + return NULL; // No autocomplete +} +``` + +## Key Files +- [`src/command/cmd_defs.c`](src/command/cmd_defs.c) - Command definitions +- [`src/command/cmd_defs.h`](src/command/cmd_defs.h) - Type definitions +- [`src/command/cmd_funcs.c`](src/command/cmd_funcs.c) - Handler implementations +- [`src/command/cmd_funcs.h`](src/command/cmd_funcs.h) - Function declarations +- [`src/command/cmd_ac.c`](src/command/cmd_ac.c) - Autocomplete +- [`src/tools/parser.c`](src/tools/parser.c) - Argument parsing +- [`src/ui/ui.h`](src/ui/ui.h) - Console output functions diff --git a/skills/cproof-structure.md b/skills/cproof-structure.md new file mode 100644 index 0000000..a0d8144 --- /dev/null +++ b/skills/cproof-structure.md @@ -0,0 +1,129 @@ +--- +name: cproof-structure +description: Always use this skill before editing CProof program. It contains file structure and expected code style. +--- + +# Cproof Structure + +## CProof Project Structure (for skill context) + +**CProof** is a terminal-based XMPP (chat) client written in C. It uses autotools for building and cmocka for unit testing. CProof is a fork of Profanity, renaming is WIP. + +### Build System + +- `configure.ac` / `Makefile.am` - Autotools build configuration +- `autogen.sh` - Script to generate configure scripts +- `configure-debug` - Debug build helper +- `tests/prof_cmocka.h` - Test framework header (cmocka wrapper) + +### Source Directory (`src/`) + +| Directory | Purpose | +|-----------|---------| +| `src/ai/` | AI client implementation (`ai_client.c/h`) - OpenAI/Perplexity provider support | +| `src/command/` | Command handling (`cmd_ac.c/h` - autocomplete, `cmd_defs.c/h` - command definitions, `cmd_funcs.c/h` - command implementations) | +| `src/config/` | Configuration management (accounts, preferences, themes, colors, CA certs, scripts) | +| `src/event/` | Event handling (client events, server events, common) | +| `src/omemo/` | OMEMO encryption (crypto, store) | +| `src/otr/` | OTR encryption (`otr.c/h`, `otrlib.c/h`, `otrlibv4.c`) | +| `src/pgp/` | PGP/OpenPGP integration (`gpg.c/h`, `ox.c/h`) | +| `src/plugins/` | Plugin system (C API, Python API, autocompleters, callbacks, disco, settings, themes) | +| `src/tools/` | Utility functions (autocomplete, parser, clipboard, editor, HTTP download/upload, bookmark ignore) | +| `src/ui/` | User interface (buffers, windows, input, chat/muc/console windows, statusbar, tray, window management) | +| `src/xmpp/` | XMPP protocol layer (connection, roster, muc, presence, message, stanza, vcard, session, JID, capabilities, OMEMO, Ox, blocking, bookmark, chat state, avatar) | + +**Key files at `src/` root:** +- `main.c` - Entry point +- `profanity.c` - Application initialization/shutdown +- `common.h` - Common types/macros (including `auto_gchar` for memory management) +- `database.c` - Database layer +- `chatlog.c` - Chat log handling +- `log.c` - Logging + +### Tests Directory (`tests/`) + +| Directory | Purpose | +|-----------|---------| +| `tests/functionaltests/` | Integration tests (requires running CProof instance) | +| `tests/unittests/` | Unit tests (cmocka-based, run via `make check`) | + +**Unit test structure:** +- `tests/unittests/unittests.c` - Test runner (registers all tests) +- `tests/unittests/test_*.c/h` - Test files paired with headers +- `tests/unittests/helpers.c/h` - Test utilities +- `tests/unittests//stub_*.c` - Stub implementations for dependencies + +**Stub directories:** +- `unittests/ai/stub_ai_client.c` - AI client stubs +- `unittests/config/stub_accounts.c`, `stub_cafile.c` +- `unittests/database/stub_database.c` +- `unittests/log/stub_log.c` +- `unittests/omemo/stub_omemo.c` +- `unittests/otr/stub_otr.c` +- `unittests/pgp/stub_gpg.c`, `stub_ox.c` +- `unittests/tools/stub_*.c` +- `unittests/ui/stub_ai.c`, `stub_ui.c`, `stub_vcardwin.c` +- `unittests/xmpp/stub_*.c` + +### Key Patterns + +**Memory Management:** +- Uses `auto_gchar` macro (from `common.h`) for automatic GString/GString cleanup +- `g_free()`, `g_list_free_full()`, `g_hash_table_destroy()` for manual cleanup +- `FREE_SET_NULL()` / `GFREE_SET_NULL()` macros + +**Autocomplete System:** +- `Autocomplete` type (GList-based) defined in `tools/autocomplete.h` +- Functions: `autocomplete_new()`, `autocomplete_add()`, `autocomplete_complete()`, `autocomplete_free()` +- `autocomplete_param_with_ac()` - Complete using an Autocomplete object +- `autocomplete_param_with_func()` - Complete using a callback function +- `_autocomplete_param_common()` - Internal helper + +**Command System:** +- Commands defined in `cmd_defs.c` with `CMD_MAINFUNC()` and `CMD_TAGS()` macros +- Autocomplete registered in `cmd_ac.c` via `g_hash_table_insert(ac_funcs, "/cmd", _cmd_autocomplete)` +- `parse_args()` from `tools/parser.c` splits input into args array + +**Testing Patterns:** +- Use `will_return(func, value)` before calling `func()` to mock return values +- Use `expect_string()` for struct field verification +- Setup/teardown via `cmocka_unit_test_setup_teardown(test_func, setup, teardown)`, centralized in unittests.c +- Mock `connection_get_status()` with `will_return(connection_get_status, JABBER_CONNECTED)` if connection is needed + +### Current Work Context + +The `/ai` command autocomplete was recently added with: +- `ai_subcommands_ac` - Top-level subcommands: set, remove, start, clear, correct, providers +- `ai_set_subcommands_ac` - Set subcommands: provider, token, org +- `ai_remove_subcommands_ac` - Remove subcommands: provider +- `ai_providers_find()` - Provider name autocomplete callback (stateless, no `_last_provider_match`) +- Tests in `tests/unittests/test_ai_client.c` and `tests/unittests/test_ai_client.h` + +## Full List of `auto_` Macros (Automatic Memory Management) + +These macros use GCC's `__cleanup__` attribute to automatically free/close resources when they go out of scope. + +| Macro | Type | Cleanup Function | Description | +|-------|------|-----------------|-------------| +| `auto_gchar` | `gchar*` | `auto_free_gchar()` | Automatically frees a GLib character string | +| `auto_gcharv` | `gchar**` | `auto_free_gcharv()` | Automatically frees a GLib character string array (e.g., from `g_strsplit`) | +| `auto_char` | `char*` | `auto_free_char()` | Automatically frees a standard C string (from `strdup`) | +| `auto_guchar` | `guchar*` | `auto_free_guchar()` | Automatically frees a GLib unsigned char string (e.g., base64 decoded) | +| `auto_gfd` | `gint*` | `auto_close_gfd()` | Automatically closes a GFileDescriptor | +| `auto_FILE` | `FILE*` | `auto_close_FILE()` | Automatically closes a FILE stream | +| `auto_jid` | `Jid*` | `jid_auto_destroy()` | Automatically frees a Jid struct (defined only in `src/xmpp/jid.h`) | + +**Example usage:** +```c +auto_gchar gchar* myString = g_strdup("Hello, world!"); +// myString is automatically freed when scope exits +``` + +## License declaration + +License declaration must be concise, such as one below. Or, even better, not present in new files: commentary about file details is much more important. +``` + * Copyright (C) 2026 CProof Developers + * + * SPDX-License-Identifier: GPL-3.0-or-later WITH OpenSSL-exception + ``` \ No newline at end of file diff --git a/skills/writing-prs-issues-commits.md b/skills/writing-prs-issues-commits.md new file mode 100644 index 0000000..86f862b --- /dev/null +++ b/skills/writing-prs-issues-commits.md @@ -0,0 +1,414 @@ +--- +name: writing-prs-issues-commits +description: Guidelines for writing effective PR descriptions, GitHub issues, and git commit messages following the inverted pyramid style. Use only when you work on them. +--- + +# Writing Effective PRs, Issues, and Git Commit Messages + +## Core Principle: Inverted Pyramid + +All three artifacts (PRs, issues, commit messages) should follow the **inverted pyramid** style: the most important information comes first, with progressively finer details below. + +> When scrolling through a commit history or PR list, readers need to quickly determine relevance. The "what" and "why" must be immediately visible. + +--- + +## 1. Git Commit Messages + +### Structure + +``` +: + + + + + + +``` + +### Rules + +1. **Subject line**: imperative mood, present tense, 50 characters or less + - Good: `feat(ai): add AI client with multi-provider support` + - Bad: `fixed the bug with the ai client that was causing issues` + +2. **First paragraph**: state WHAT was changed and WHY in 2-3 sentences. No code references. + +3. **Optional "How" section**: implementation details, debugging process, architecture decisions. This is "extra-credit" reading. + +4. **Never bury the lead**: if the commit changes one file, the subject line should say what it does. Don't make readers scroll to find out. + +### Anti-Patterns (from David Thompson's "Favorite Commit") + +| Anti-Pattern | Example | Fix | +|---|---|---| +| Burying the change | 6 paragraphs before showing the diff | Put the summary first | +| Unexplained problem | "I introduced some tests..." without saying what broke | State the problem explicitly | +| Unlinked code references | "removing the .with_content() matchers" | Name the file or link to it | + +### Example + +``` +feat(ai): add AI client with multi-provider support + +Add an AI client module that integrates with OpenAI-compatible API +providers (OpenAI, Perplexity, and custom providers) to provide +AI-assisted responses within the CProof client. + +The implementation includes provider management, session handling, +async HTTP request handling via libcurl, and a dedicated AI window +for displaying conversations. + +Architecture: +- Async design: HTTP requests run on a separate thread to avoid + blocking the main UI loop +- Reference counting: Both AIProvider and AISession use ref counting + for safe shared ownership +- Response size limit: 10MB cap on HTTP responses to prevent OOM +``` + +--- + +## 2. GitHub Issues + +### Structure + +```markdown +# : + +## Motivation + + + +## Proposed Solution + + + +## Acceptance Criteria + +- [ ] AC-1: +- [ ] AC-2: +- [ ] ... + +## Open Questions + + + +## Related + + +``` + +### Rules + +1. **Title**: clear, specific, action-oriented. Use conventional commit prefixes where appropriate (`feat:`, `fix:`, `docs:`, etc.) + +2. **Motivation first**: explain WHY this matters before describing WHAT to build. Focus on user value, not technical details. + +3. **Acceptance criteria**: verifiable, testable conditions. Use function names or command syntax where helpful, but avoid implementation specifics (no "use GHashTable" or "add this function signature"). + +4. **Keep it general**: describe the feature, not the implementation. The "how" is for the PR/commit, not the issue. + +5. **Commands over code**: when describing user-facing behavior, use command syntax (`/ai start`) rather than function names (`ai_window_create()`). + +### Anti-Patterns + +| Anti-Pattern | Example | Fix | +|---|---|---| +| Implementation details in issue | "Add a GHashTable in ai_client.c" | Describe the feature: "Users can manage multiple AI providers" | +| Vague acceptance criteria | "Make it work" | "Running `/ai start` opens a dedicated AI conversation window" | +| No motivation | Jumping straight to the solution | Explain WHY this matters to users | + +### Example + +```markdown +# feat(ai): AI Client with Multi-Provider Support + +## Motivation + +CProof users want AI-assisted responses without leaving the chat client. +This feature enables users to query AI models directly from within CProof +for quick answers, message drafting, or knowledge retrieval. + +### Privacy First + +- No telemetry: No data is sent to CProof or any third party except + the configured AI provider +- Local-first: Any OpenAI-compatible API endpoint works, including + local servers (Ollama, LM Studio, vLLM) +- User-controlled keys: API keys are stored per-provider in the + preferences file, never hardcoded + +## Proposed Feature + +Add an AI client module to CProof that lets users interact with AI +providers from within the chat interface. + +A user should be able to: +1. Start an AI session with `/ai start` +2. See a dedicated AI window open for the conversation +3. Send messages and receive AI responses +4. Switch between providers and models +5. Use tab-completion for provider names (`/ai s`) +6. Have providers and API keys persist across CProof sessions + +## Acceptance Criteria + +### Core Functionality + +- [ ] AC-1: Running `/ai start` opens a dedicated AI conversation window +- [ ] AC-2: The AI window displays the user's prompt and the AI's response +- [ ] AC-3: AI responses are properly formatted, including multiline responses +- [ ] AC-4: The conversation maintains history within a session +- [ ] AC-5: Running `/ai stop` closes the AI window and ends the session + +### Provider Management + +- [ ] AC-6: Default providers are available out of the box +- [ ] AC-7: Users can add custom providers with `/ai provider add ` +- [ ] AC-8: Users can list available providers with `/ai provider list` + +### API Key Management + +- [ ] AC-9: Users can set an API key for a provider with `/ai key ` +- [ ] AC-10: API keys persist across CProof sessions +- [ ] AC-11: API keys are not displayed in plain text when listed + +### Autocomplete + +- [ ] AC-12: `/ai s` autocompletes to available providers +- [ ] AC-13: Autocomplete works for custom providers added by the user + +### Error Handling + +- [ ] AC-14: If the API key is invalid, the user sees a clear error message +- [ ] AC-15: If the provider is unreachable, the user sees a clear error message + +## Open Questions + +1. Should we support streaming responses (displaying text as it arrives)? +2. Should there be a rate limit or timeout for AI responses? + +## Related + +- [OpenAI Responses API](https://platform.openai.com/docs/api-reference/responses) +- [Ollama](https://ollama.com/) (local server) +``` + +--- + +## 3. Pull Request Descriptions + +### Structure + +```markdown +# : + +## Introduced change + +<2-3 sentences: what this PR does and why it matters> + +### Capabilities + + + +## Reasoning behind the change + + + +## Implementation details + + + +## Testing + + + +## Screenshots (if UI changes) + + + +Resolves # +``` + +### Rules + +1. **"Introduced change"**: the first section should answer "what" without requiring the reader to scroll. Use bullet points for capabilities. + +2. **"Reasoning behind the change"**: explain design decisions, trade-offs, and alternatives considered. This is where you justify your approach. + +3. **"Implementation details"**: architecture diagrams, code snippets, data structures. This is "extra-credit" reading for reviewers who want to understand the internals. + +4. **No git diff stat**: don't include "X files changed, +Y lines". That's visible in the PR UI. + +5. **Commands over code**: when describing user-facing behavior, use command syntax rather than function names. + +6. **Implicit sections**: the sections flow naturally — "Introduced change" covers what, "Reasoning" covers why, "Implementation details" covers how. Don't label them explicitly as "What/Why/How". + +### Anti-Patterns + +| Anti-Pattern | Example | Fix | +|---|---|---| +| Git diff stat | "20 files changed, +2400 insertions" | Remove — visible in PR UI | +| Implementation in "What" | "Added ai_client.c with 843 lines" | Describe the feature: "Core AI client with provider management" | +| No "Why" section | Just listing changes | Explain design decisions and trade-offs | + +### Example + +```markdown +# feat(ai): add AI client with multi-provider support and UI + +## Introduced change + +An AI client module that integrates with OpenAI-compatible API providers +to deliver AI-assisted responses within the CProof chat client. Users can +create AI sessions, send prompts, and view responses in a dedicated AI +window — all from within the terminal UI. + +### Capabilities + +- **Multi-provider support**: Ships with OpenAI and Perplexity as defaults; + add custom providers via `/ai provider add` +- **Per-provider API keys**: Each provider's API key is stored in the + preferences system +- **Conversation history**: Sessions maintain message history (user/assistant + turns) for context-aware responses +- **Async requests**: HTTP calls run on a background thread; responses and + errors invoke callbacks on the main UI thread +- **Model selection**: Switch between models per session (e.g., `gpt-4`, `sonar`) +- **Provider autocomplete**: Tab-completion for provider names in commands + +## Reasoning behind the change + +CProof is an XMPP client focused on privacy and usability. Adding AI support +gives users a way to get quick answers, message drafting assistance, or +knowledge retrieval without leaving the chat client. + +The design prioritizes: + +1. **Privacy**: API keys are stored per-provider in preferences, not hardcoded. + Users control which providers they use. No data is shared with us, local + OpenAI-compatible servers are also supported. +2. **Extensibility**: The provider abstraction (`AIProvider` struct with name, + URL, org_id) makes it trivial to add new providers. +3. **Non-blocking UI**: Async HTTP ensures the terminal UI remains responsive + during API calls. +4. **Safety**: Response size capped at 10MB; reference counting prevents + use-after-free on shared objects. + +## Implementation details + +### Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────┐ +│ UI Layer │ +│ ┌──────────────┐ ┌──────────────┐ ┌───────────────────┐ │ +│ │ ProfAiWin │ │ /ai command │ │ Provider autocomplete│ │ +│ │ (window.c) │ │ (cmd_funcs) │ │ (cmd_ac.c) │ │ +│ └──────┬───────┘ └──────┬───────┘ └────────┬──────────┘ │ +│ │ │ │ │ +│ ▼ ▼ │ │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ AI Client (ai_client.c) │ │ +│ │ ┌────────────┐ ┌────────────┐ ┌───────────┐ │ │ +│ │ │ Providers │ │ Sessions │ │ curl HTTP│ │ │ +│ │ │ (GHashTable)│ │(ref-counted)│ │(async) │ │ │ +│ │ └────────────┘ └────────────┘ └───────────┘ │ │ +│ └──────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ Preferences (config/preferences.c) │ │ +│ │ Stores: api_keys[provider_name] │ │ +│ └──────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Key Implementation Details + +**Provider Management** — Providers are stored in a `GHashTable` keyed by name. +Each provider has a reference count for safe shared ownership: + +```c +typedef struct ai_provider_t { + gchar* name; + gchar* api_url; + gchar* org_id; + GList* models; + guint ref_count; +} AIProvider; +``` + +Default providers are registered during `ai_client_init()`: + +| Provider | URL | +|---|---| +| `openai` | `https://api.openai.com/v1/responses` | +| `perplexity` | `https://api.perplexity.ai/v1/responses` | + +**Session Lifecycle** — Sessions track conversation history and are reference-counted: + +```c +typedef struct ai_session_t { + gchar* provider_name; + AIProvider* provider; + gchar* model; + gchar* api_key; + GList* history; // GList of AIMessage* + guint ref_count; +} AISession; +``` + +#### Async HTTP Request + +`ai_send_prompt()` spawns a GThread that: +1. Builds a JSON body from the session's conversation history +2. Sends a POST request via libcurl with the provider's API key +3. Invokes the user's callback on the main thread with the response or error + +Response size is capped at 10MB to prevent OOM conditions. + +#### UI Integration + +A new `ProfAiWin` window type displays AI conversations. Responses are streamed +line-by-line; errors are displayed in red. + +#### Testing + +Unit tests cover: +- Provider CRUD (add, remove, list, update) +- Session lifecycle (create, ref/unref, message history, clear) +- API key get/set +- JSON string escaping (special chars, backslashes, percent signs) +- Provider autocomplete (forward, backward, partial match, case sensitivity) + +Resolves #110 +``` + +--- + +## Quick Reference + +### Conventional Commits Prefixes + +| Prefix | Meaning | +|---|---| +| `feat` | New feature | +| `fix` | Bug fix | +| `docs` | Documentation changes | +| `style` | Code style changes (formatting, semicolons, etc.) | +| `refactor` | Code changes that neither fix a bug nor add a feature | +| `test` | Adding or modifying tests | +| `chore` | Build process, auxiliary tools, CI changes | +| `perf` | Performance improvements | +| `ci` | CI configuration changes | +| `build` | Build system or dependency changes | + +### File Naming + +- CProof (not Profanity) — this is a fork +- Use snake_case for files: `ai_client.c`, `ai_client.h` +- Use snake_case for functions: `ai_session_create()` +- Use PascalCase for types: `AIProvider`, `AISession`