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:
109
patterns/commands.md
Normal file
109
patterns/commands.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Commands
|
||||
|
||||
## Three files, three concerns
|
||||
|
||||
| File | Holds |
|
||||
|---|---|
|
||||
| `src/command/cmd_defs.c` (`cmd_defs.h`) | The big static array of `Command` records — every `/foo` is one entry. |
|
||||
| `src/command/cmd_funcs.c` (`cmd_funcs.h`) | Handler implementations: `gboolean cmd_<name>(ProfWin*, const char* const, gchar**)`. |
|
||||
| `src/command/cmd_ac.c` (`cmd_ac.h`) | Per-command autocomplete logic and registration. |
|
||||
|
||||
## Anatomy of a command record
|
||||
|
||||
Defined inline in `cmd_defs.c` using uppercase macros (declared in `cmd_defs.c`):
|
||||
|
||||
```c
|
||||
{ CMD_PREAMBLE("/caps",
|
||||
parse_args, 0, 1, NULL)
|
||||
CMD_MAINFUNC(cmd_caps)
|
||||
CMD_TAGS(
|
||||
CMD_TAG_DISCOVERY,
|
||||
CMD_TAG_CHAT,
|
||||
CMD_TAG_GROUPCHAT)
|
||||
CMD_SYN(
|
||||
"/caps",
|
||||
"/caps <fulljid>|<nick>")
|
||||
CMD_DESC("...")
|
||||
CMD_ARGS(
|
||||
{ "<fulljid>", "..." },
|
||||
{ "<nick>", "..." })
|
||||
CMD_EXAMPLES(
|
||||
"/caps user@host/res")
|
||||
}
|
||||
```
|
||||
|
||||
| Macro | Role |
|
||||
|---|---|
|
||||
| `CMD_PREAMBLE(name, parse_fn, min, max, pre_hook)` | Command name, parser, arg count bounds, optional pre-hook. |
|
||||
| `CMD_MAINFUNC(fn)` | Single handler for the whole command. |
|
||||
| `CMD_SUBFUNCS({"sub", fn}, ...)` | Dispatch by first argument. Use **instead of** `CMD_MAINFUNC` when the command has subcommands (e.g. `/status get`, `/status set`). |
|
||||
| `CMD_TAGS(...)` | One or more `CMD_TAG_*` (UI grouping, search). |
|
||||
| `CMD_SYN(...)` | Synopsis lines for `/help <cmd>`. |
|
||||
| `CMD_DESC(...)` | Long description. |
|
||||
| `CMD_ARGS({"name", "desc"}, ...)` | Argument table for help. |
|
||||
| `CMD_EXAMPLES(...)` | Examples shown in help. |
|
||||
|
||||
The full set of `CMD_*` macro definitions is at the top of `cmd_defs.c` —
|
||||
read them when authoring a new command.
|
||||
|
||||
## Argument parsing
|
||||
|
||||
`parse_args()` lives in `src/tools/parser.c`:
|
||||
|
||||
```c
|
||||
gchar** parse_args(const char* const inp, int min, int max, gboolean* result);
|
||||
```
|
||||
|
||||
- Splits on whitespace, honours quoted segments.
|
||||
- Returns `NULL` on parse failure (sets `*result = FALSE`).
|
||||
- The handler receives the result as `gchar** args` — free is owned by the
|
||||
caller in `cmd_defs.c`, the handler does **not** free.
|
||||
|
||||
Parser variants used in `CMD_PREAMBLE`:
|
||||
- `parse_args` — standard quoted-aware split.
|
||||
- `parse_args_with_freetext` — last argument is the rest of the line, no
|
||||
splitting (used by `/msg`, `/me`, etc.).
|
||||
- `parse_args_as_one` — concatenates remaining tokens into a single string.
|
||||
|
||||
## Handler signature
|
||||
|
||||
```c
|
||||
gboolean cmd_foo(ProfWin* window, const char* const command, gchar** args);
|
||||
```
|
||||
|
||||
Return `TRUE` on success, `FALSE` to print bad-usage. `command` is the literal
|
||||
`/foo` typed (useful when the same handler is registered under multiple names).
|
||||
|
||||
## Connection-state gates
|
||||
|
||||
Most commands that touch XMPP must verify connection state first:
|
||||
|
||||
```c
|
||||
if (connection_get_status() != JABBER_CONNECTED) {
|
||||
cons_show("You are not currently connected.");
|
||||
return TRUE;
|
||||
}
|
||||
```
|
||||
|
||||
`cons_bad_cmd_usage(cmd)` is the canonical "wrong arguments" path; pass the
|
||||
literal command name.
|
||||
|
||||
## Adding a new command
|
||||
|
||||
See `playbooks/add-command.md` for the full walkthrough. Short version:
|
||||
|
||||
1. New entry in `cmd_defs.c` with `CMD_PREAMBLE`/`CMD_MAINFUNC`/...
|
||||
2. Implement `cmd_<name>()` in `cmd_funcs.c` (declare in `cmd_funcs.h`).
|
||||
3. (If autocompletion needed) implement `_<name>_autocomplete()` in
|
||||
`cmd_ac.c` and register it via `g_hash_table_insert(ac_funcs, "/name", ...)`.
|
||||
4. Add a unit test pair `tests/unittests/test_cmd_<name>.{c,h}` and register
|
||||
tests in `tests/unittests/unittests.c`.
|
||||
|
||||
## Conventions
|
||||
|
||||
- `cmd_<name>` for handlers; `_<name>_autocomplete` (file-static) for the
|
||||
autocomplete callback.
|
||||
- Subcommand dispatch via `CMD_SUBFUNCS` is preferred over `if/else` chains
|
||||
inside `cmd_<name>` when possible.
|
||||
- Keep handlers in `cmd_funcs.c` thin: parse & validate args, then delegate
|
||||
to a domain module (`xmpp/`, `config/`, `ui/`).
|
||||
Reference in New Issue
Block a user