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.5 KiB
Gotchas
Append-only log of known pitfalls. One entry per pitfall. Format:
## YYYY-MM-DD — <short title>
<body — what bites, why, what to do instead>
Scan this file before finishing any non-trivial task.
2026-04-30 — g_strsplit result must use g_strfreev
g_strsplit (and g_strdupv) return a gchar** whose elements are
individually GLib-allocated. Free with g_strfreev, not g_free. The
latter compiles fine and silently leaks the inner strings.
The cleanup macro is auto_gcharv (declared in src/common.h):
auto_gcharv gchar** parts = g_strsplit(line, " ", -1);
Mixing g_free on a gchar** is the most common allocator-mismatch bug in
this codebase.
2026-04-30 — JABBER_CONNECTED must be queued in unit tests
Most XMPP-touching code paths gate on connection_get_status(). The unit-
test stub returns mock(), so every test reaching that gate must:
will_return(connection_get_status, JABBER_CONNECTED);
// or
will_return(connection_get_status, JABBER_DISCONNECTED);
Forgetting this aborts cmocka with a "missing mock value" error. If you see
that error, it's almost always connection_get_status you forgot.
2026-04-30 — Autocomplete callbacks must be stateless
Some older callbacks keep "last match" state in a file-static _last_*_match
variable. Do not copy that pattern. A clean canonical example is
roster_contact_autocomplete (src/xmpp/roster_list.c) — it delegates
straight to autocomplete_complete against a roster-owned Autocomplete
object and keeps no callback-local state.
Stateful callbacks misbehave under shift-tab cycling and break when two
completers run concurrently in a layered command (e.g. /foo set bar <tab>
followed by /foo set baz <tab>).
2026-04-30 — CWE-134 format-string discipline
Never pass user-controlled or arbitrary strings as the format argument to
printf-family or GLib formatted-print functions. A recent compiler-
hardening pass (cf. cproof commit 9ec01fa8c) audited and tightened these
sites; do not regress.
Right:
cons_show("%s", user_text);
Wrong:
cons_show(user_text);
The compiler does not always warn, especially across helper layers. Enable
and respect -Wformat -Wformat-security warnings in any build you trust.
2026-04-30 — Build inside Docker, not on the host
Library version drift between a developer host and Dockerfile.debian
(GLib, OpenSSL, libstrophe in particular) can mask or invent failures. CI
is the source of truth; CI builds inside the Debian image. See
build/docker.md.
If you absolutely must build on the host, mirror the package set in
Dockerfile.debian exactly and pin versions — you will spend more time
chasing version drift than the Docker build would have cost.
2026-04-30 — Beware of "Profanity" vs "cproof" naming
The fork-rename is incomplete. Many files, symbols, copyright headers, and
config keys still say Profanity / prof_ / profanity. These are
authoritative as-is; do not rename them opportunistically alongside
unrelated changes. Rename work, when it happens, will be a dedicated PR.
2026-04-30 — Encryption disable builds must compile
Each encryption stack (HAVE_OMEMO, HAVE_LIBOTR, HAVE_LIBGPGME) can be
turned off at configure time. New code that calls into these modules
must wrap calls in the matching #ifdef and provide a graceful fallback
in the #else. CI does not always test every disable combination — verify
locally with at least the off-build for each stack you touched.