3 Commits

Author SHA256 Message Date
9020af7407 delete incorrectly uploaded skill 2026-04-30 18:21:23 +00:00
1fb2940ba8 fix skills 2026-04-30 18:21:04 +00:00
a9e62fc2cb Add skills 2026-04-30 18:18:50 +00:00
3 changed files with 847 additions and 0 deletions

304
skills/command-system.md Normal file
View File

@@ -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 <name> <url>", "/ai set token <provider> <token>")
CMD_DESC("Interact with AI models via OpenAI-compatible APIs.")
CMD_ARGS({ "", "Display current AI settings" },
{ "set provider <name> <url>", "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 [<arg>]")
CMD_DESC("My new command description.")
CMD_ARGS({ "<arg>", "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

129
skills/cproof-structure.md Normal file
View File

@@ -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/<module>/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
```

View File

@@ -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
```
<type>: <short summary (50 chars max)>
<blank line>
<one paragraph describing the change and its motivation>
<optional: implementation details, debugging process, architecture notes>
```
### 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
# <type>: <issue title>
## Motivation
<Why does this matter? Who benefits?>
## Proposed Solution
<What should happen? Keep it implementation-agnostic.>
## Acceptance Criteria
- [ ] AC-1: <verifiable condition>
- [ ] AC-2: <verifiable condition>
- [ ] ...
## Open Questions
<Unresolved decisions that need input>
## Related
<Links to related issues, docs, or external resources>
```
### 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<tab>`)
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 <name> <url>`
- [ ] 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 <provider> <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<tab>` 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
# <type>: <PR title>
## Introduced change
<2-3 sentences: what this PR does and why it matters>
### Capabilities
<Bullet list of key features/capabilities>
## Reasoning behind the change
<Motivation, design decisions, trade-offs considered>
## Implementation details
<Architecture overview, key implementation details, code snippets if helpful>
## Testing
<How to run tests, what was tested>
## Screenshots (if UI changes)
<Before/after images>
Resolves #<issue-number>
```
### 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`