Files
cproof-context/playbooks/add-command.md
jabber.developer2 22977846a3 docs: split context into layered, agent-oriented files
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.
2026-04-30 20:52:06 +03:00

3.6 KiB

Playbook: add a /command

End-to-end recipe for adding a new user-facing command. Example: /foo.

1. Define the handler

src/command/cmd_funcs.h:

gboolean cmd_foo(ProfWin* window, const char* const command, gchar** args);

src/command/cmd_funcs.c:

gboolean
cmd_foo(ProfWin* window, const char* const command, gchar** args)
{
    if (connection_get_status() != JABBER_CONNECTED) {
        cons_show("You are not currently connected.");
        return TRUE;
    }
    if (!args[0]) {
        cons_bad_cmd_usage(command);
        return TRUE;
    }
    // ... do the thing ...
    return TRUE;
}

Keep the handler thin: validate, then delegate to a domain module.

2. Register the command

src/command/cmd_defs.c — add an entry to the static Command array:

{ CMD_PREAMBLE("/foo",
               parse_args, 0, 1, NULL)
  CMD_MAINFUNC(cmd_foo)
  CMD_TAGS(CMD_TAG_CHAT)
  CMD_SYN("/foo [<arg>]")
  CMD_DESC("Do the foo thing.")
  CMD_ARGS(
      { "<arg>", "Optional argument to pass." })
  CMD_EXAMPLES("/foo bar")
}

For a command with subcommands, swap CMD_MAINFUNC(cmd_foo) for CMD_SUBFUNCS({"sub", cmd_foo_sub}, ...).

3. Autocompletion (optional)

src/command/cmd_ac.c:

a. (If a static token list) declare and initialise an Autocomplete:

static Autocomplete foo_ac;

// in cmd_ac_init():
foo_ac = autocomplete_new();
autocomplete_add(foo_ac, "bar");
autocomplete_add(foo_ac, "baz");

// add &foo_ac to the static free-list near top of file

b. Implement the per-command dispatcher:

static char*
_foo_autocomplete(ProfWin* window, const char* const input, gboolean previous)
{
    return autocomplete_param_with_ac(input, "/foo", foo_ac, TRUE, previous);
}

c. Register it:

g_hash_table_insert(ac_funcs, "/foo", _foo_autocomplete);

For dynamic suggestions, use autocomplete_param_with_func and a stateless callback. See patterns/autocomplete.md.

4. Unit test

Create tests/unittests/test_cmd_foo.c and test_cmd_foo.h:

test_cmd_foo.h:

void test_cmd_foo_when_disconnected_shows_message(void** state);
void test_cmd_foo_when_no_arg_shows_usage(void** state);
void test_cmd_foo_happy_path(void** state);

test_cmd_foo.c:

#include "config.h"
#include "prof_cmocka.h"
#include "test_cmd_foo.h"
// ... includes for stubs and the unit ...

void
test_cmd_foo_when_disconnected_shows_message(void** state)
{
    will_return(connection_get_status, JABBER_DISCONNECTED);
    expect_string(cons_show, msg, "You are not currently connected.");
    gchar* args[] = { NULL };
    assert_true(cmd_foo(NULL, "/foo", args));
}

5. Register the test

tests/unittests/unittests.c:

#include "test_cmd_foo.h"

// ... inside the tests[] array ...
cmocka_unit_test(test_cmd_foo_when_disconnected_shows_message),
cmocka_unit_test(test_cmd_foo_when_no_arg_shows_usage),
cmocka_unit_test(test_cmd_foo_happy_path),

6. Stubs

If cmd_foo calls a function that is not yet stubbed, add the stub. See testing/stubs.md.

7. Wire into Make

  • cmd_defs.c, cmd_funcs.c, cmd_ac.c are already in the build.
  • New test file: add tests/unittests/test_cmd_foo.c to the unittests sources in tests/unittests/Makefile.am (or whatever wires it).
  • New stub file (if any): add to the same Makefile.

8. Build & check

Inside Docker (build/docker.md):

./autogen.sh && ./configure && make -j$(nproc) && make check

9. Help text

/help foo should now produce the synopsis / description / args from the Command entry. No separate help file to update.

10. Commit

Single commit, conventional-commit style, English. No AI-attribution trailer.