Replace the single file-structure.md with a stratified layout designed
for AI/agent skill consumption: tables and concrete identifiers over
prose, files loaded on demand, content separated by churn rate.
Layers:
- architecture/ stable structural reference (overview, source-map,
test-map, data-flow)
- patterns/ memory, commands, autocomplete, events, xmpp,
encryption, ui, plugins
- testing/ unit-tests, stubs, functional-tests, bench
- build/ local, docker, ci
- playbooks/ add-command, add-test, add-autocomplete,
add-event-handler, add-encryption
- gotchas.md append-only dated entries (seven seed entries)
- wip/ branch-specific notes; deleted on merge to master
Stable layers describe cproof on master only. In-flight feature
branches (currently feat/ai) get a single file under wip/.
INDEX.md is the entry map with churn labels; SKILL.md is the
always-loaded skill hint pointing to it.
3.3 KiB
Playbook: add autocomplete to a command
Pre-req: command already exists in cmd_defs.c. If not, see
playbooks/add-command.md.
Choose the flavour
| Token set | Use |
|---|---|
| Static, known at startup. | autocomplete_param_with_ac + a static Autocomplete. |
| Dynamic — depends on roster, accounts, DB query, etc. | autocomplete_param_with_func + a stateless callback. |
Static token set
src/command/cmd_ac.c:
// 1. Declare the AC at file scope:
static Autocomplete foo_ac;
// 2. Add it to the static array near the top of the file (the free-list
// that cmd_ac_uninit walks at shutdown):
static Autocomplete* all_acs[] = {
// ... existing entries ...
&foo_ac,
};
// 3. Initialise in cmd_ac_init():
foo_ac = autocomplete_new();
autocomplete_add(foo_ac, "alpha");
autocomplete_add(foo_ac, "beta");
// 4. Implement the dispatcher:
static char*
_foo_autocomplete(ProfWin* window, const char* const input, gboolean previous)
{
return autocomplete_param_with_ac(input, "/foo", foo_ac, TRUE, previous);
}
// 5. Register:
g_hash_table_insert(ac_funcs, "/foo", _foo_autocomplete);
Dynamic — function callback
If suggestions come from runtime state (roster, accounts, providers list):
// In a domain module (e.g. src/foo/foo.c), implement a stateless callback:
char*
foo_suggestions(const char* const search_str, gboolean previous, void* context)
{
// Compute and return a freshly-allocated char* (caller frees), or NULL.
// No module-level "_last_match" globals — keep state on the stack /
// derive deterministically from search_str and previous.
}
Wire into cmd_ac.c:
static char*
_foo_autocomplete(ProfWin* window, const char* const input, gboolean previous)
{
return autocomplete_param_with_func(input, "/foo", foo_suggestions, previous, NULL);
}
g_hash_table_insert(ac_funcs, "/foo", _foo_autocomplete);
A clean canonical example is roster_contact_autocomplete in
src/xmpp/roster_list.c — it delegates to autocomplete_complete against a
roster-owned Autocomplete and keeps no callback-local state. Do not
copy older callbacks that keep state in a file-static _last_match
variable — those have known issues with shift-tab cycling and concurrent
completers.
Subcommand autocompletion
If /foo has subcommands, branch inside _foo_autocomplete:
static char*
_foo_autocomplete(ProfWin* window, const char* const input, gboolean previous)
{
char* result = NULL;
// First the subcommand list itself:
result = autocomplete_param_with_ac(input, "/foo", foo_subcommands_ac, TRUE, previous);
if (result) return result;
// Then per-subcommand argument completion:
if (g_str_has_prefix(input, "/foo set ")) {
result = autocomplete_param_with_func(input, "/foo set", foo_set_options, previous, NULL);
if (result) return result;
}
return NULL;
}
/roster (src/command/cmd_ac.c, search for _roster_autocomplete) is a
current example of this layered subcommand layout.
Unit-test the callback
Stateless callbacks are easy to unit-test directly:
void
test_foo_suggestions_returns_first_match(void** state)
{
char* r = foo_suggestions("al", FALSE, NULL);
assert_string_equal(r, "alpha");
free(r);
}
Add the test pair to tests/unittests/ (see playbooks/add-test.md).