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.
This commit is contained in:
161
playbooks/add-command.md
Normal file
161
playbooks/add-command.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# 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`:
|
||||
|
||||
```c
|
||||
gboolean cmd_foo(ProfWin* window, const char* const command, gchar** args);
|
||||
```
|
||||
|
||||
`src/command/cmd_funcs.c`:
|
||||
|
||||
```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:
|
||||
|
||||
```c
|
||||
{ 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`:
|
||||
|
||||
```c
|
||||
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:
|
||||
|
||||
```c
|
||||
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:
|
||||
|
||||
```c
|
||||
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`:
|
||||
|
||||
```c
|
||||
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`:
|
||||
|
||||
```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`:
|
||||
|
||||
```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`):
|
||||
|
||||
```sh
|
||||
./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.
|
||||
Reference in New Issue
Block a user