summaryrefslogtreecommitdiffstats
path: root/CLAUDE.md
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-13 09:48:50 +0200
committerDanilo M. <danix@danix.xyz>2026-08-13 09:48:50 +0200
commitfa118ca26428e46aff4e8ccff72d3a6e2bde7039 (patch)
tree20580c3117ba336e0c81225e52aac6406457cb98 /CLAUDE.md
parent4929cb81ac2cda79e4f4b411711130d6e0f8173f (diff)
downloadqtmaildir-fa118ca26428e46aff4e8ccff72d3a6e2bde7039.tar.gz
qtmaildir-fa118ca26428e46aff4e8ccff72d3a6e2bde7039.zip
docs: record the one coupling between qtmaildir and mailctl
The two tools are independent except for ~/.config/mailrules/rules.json, which has two independent implementations agreeing by test rather than by shared code. That is the only way work here can break mailctl, so it now has a named procedure: change both readers, bump the format version only for a breaking change, run both suites, and verify the round trip by hand since no automated test spans the repos. Also records that the backlog covers the mail system rather than this binary alone. Item 44 already shipped as commits in both repos and future format work will too.
Diffstat (limited to 'CLAUDE.md')
-rw-r--r--CLAUDE.md60
1 files changed, 60 insertions, 0 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index c9d6cb3..d652bb6 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -214,6 +214,14 @@ completion descriptions. Query syntax itself is not user-facing text — notmuch
like `tag:` and `date:` are wire format and must never be translated, only the prose
describing them. Pre-existing code has not been audited against this rule.
+**This application has a sibling, and one file couples them.** `mailctl`
+(`../mailctl`) is a narrow, agent-safe CLI over the same notmuch index. The two
+are independent except for `~/.config/mailrules/rules.json`, which both read and
+write. **Before changing anything about that file's format, read
+"Changing the shared rule format" at the bottom of this document.** Nothing else
+here can break mailctl: it never imports from this repo, and this repo never
+calls it.
+
**The auto-tagging rules are NOT in this repo, and notmuch's parser rejects
almost nothing.** Rules live in `~/.config/mailrules/rules.json`, applied by a
notmuch `post-new` hook that ships from the companion `mailctl` project;
@@ -373,6 +381,13 @@ backlog had already specified and that shipped unbuilt (item 29). A note saying
"X does not work" is a bug report, and it will sit in a personal notes file
indefinitely unless someone goes looking.
+**The backlog covers the mail system, not only this binary.** Item 44 shipped as
+commits in BOTH this repo and `../mailctl`, and any future item touching the
+shared rule format will too. An item is not "not ours" because its work lands in
+the sibling repo; note where the work goes in the table's Note column. mailctl
+keeps its own `TODO.md` for things that are purely its own, and that file is not
+part of this reconciliation.
+
**Then print the open items as a table, and stop.** The user picks what to work
on; do not start on one, and do not recommend a single item as though the choice
were made. Read the status table for anything not marked `done`, `dropped` or
@@ -463,3 +478,48 @@ button style, and both are things a user notices without reading the changelog.
`assets/slackbuild/` carries its own version in three files and is **not** part
of this procedure. It has been stale since 0.7.0 and the user is considering
removing it; do not bump it as a side effect of a release.
+
+## Changing the shared rule format
+
+`~/.config/mailrules/rules.json` has **two independent implementations**, and
+they agree by test rather than by sharing code:
+
+| | reads/writes | applies rules |
+|---|---|---|
+| `src/tagrules.cpp` (here) | yes | no |
+| `mailrules.py` (`../mailctl`) | yes | via the `post-new` hook |
+
+**This is the only way work here can break mailctl.** It never imports from this
+repo and this repo never calls it, so nothing else is shared. The file is
+deliberately owned by neither: both readers preserve fields they do not
+understand (`TagRule::unknown`, `Rule.unknown`), which is what lets one tool
+save a file the other wrote without stripping it.
+
+**A format change is therefore a two-repo change, and the live hook runs every
+ten minutes on real mail.** Before touching the schema:
+
+1. Change both readers, not one. A field added here and not there is silently
+ dropped on the next save from the other side, which looks like data loss with
+ no error anywhere.
+2. Bump `kFormatVersion` / `FORMAT_VERSION` together only for a BREAKING change.
+ Both readers refuse a file whose version they do not know, which is the
+ correct behaviour and also means a half-deployed bump stops the hook from
+ tagging. Adding an optional field needs no bump.
+3. Run both suites: `ctest --test-dir build -R tagrules` here, and
+ `./test_mailrules.py && ./test_post_new.py` there.
+4. Verify the round trip across tools by hand, since no automated test spans
+ both repos: save from the dialog, then `mailctl rules list`, and confirm the
+ rule count and a note survive.
+
+**Two hook properties are safety-critical and are not this repo's to weaken.**
+The hook refuses to remove `unread` or `inbox` (`maildir.synchronize_flags` is
+true, so removing `unread` rewrites Maildir filenames and reaches the server),
+and it does not consume the `tag:new` marker when the rules fail to load
+(clearing it while rules did not run orphans that mail permanently and
+invisibly). A dialog here that offers to write such a rule would produce one the
+hook then refuses; that is the correct direction, but say so in the UI rather
+than letting it fail silently.
+
+Backfill, applying a rule to existing mail, is deliberately unbuilt. See
+`docs/superpowers/specs/2026-08-12-tagging-rules-design.md` for what it needs
+first, including the revision it forces to the no-confirmation rule above.