Compare commits
3 Commits
draft/spli
...
skills-exa
| Author | SHA256 | Date | |
|---|---|---|---|
| 9020af7407 | |||
| 1fb2940ba8 | |||
| a9e62fc2cb |
304
skills/command-system.md
Normal file
304
skills/command-system.md
Normal 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
129
skills/cproof-structure.md
Normal 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
|
||||||
|
```
|
||||||
414
skills/writing-prs-issues-commits.md
Normal file
414
skills/writing-prs-issues-commits.md
Normal 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`
|
||||||
Reference in New Issue
Block a user