Refactor the CI pipeline to use WithServiceBinding for the Stalwart mail
server, replacing legacy shell scripts and manual port management.
Introduces pre-seeded data for the Stalwart service to avoid network
hits and improves headless UI testing with Xvfb.
- Add LocalSieveApplied table (schema v32) keyed by (accountId, messageId)
so each email is processed by Sieve at most once, even across restarts.
- Implement EmailRepository.applySieveRules(): loads the active local Sieve
script, runs the interpreter against new INBOX emails, and queues pending
move/delete/flag_seen changes for any matched rules.
- Wire applySieveRules() into both _AccountSync._sync() and
_JmapAccountSync._sync() after the per-mailbox email sync loop.
- Make _flushPendingChangesImap() treat NONEXISTENT / not-found errors as
silent no-ops (counts as flushed) so a second device racing on the same
email does not accumulate retries.
- Add migration test assertions and a dedicated unit test suite covering
rule matching, deduplication, discard, and multi-email processing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without flutter pub get, .dart_tool/package_config.json does not exist
in the Dagger container. dart format then defaults to the current SDK
version (3.11+) rather than the package's declared language version
(3.3), applying tall-style formatting and failing on 90 files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without pubspec.lock, flutter pub get in the Dagger container resolves
package versions independently of the local lockfile. This caused
flutter_lints to be unresolvable in the container, making dart format
fall back to a different formatter style and flag 90 files as changed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without pub get, dart format cannot resolve package URIs and uses a
different language version, causing spurious failures for correctly
formatted files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add TestMain class covering the main() flow: asserts that _set_labels
is called with State/InProgress (and State/Ready removed) strictly
before _start_agent, and that no labels or agents are touched when
there are no ready issues.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the tmux-based agent launcher with a direct subprocess.Popen
call. Claude sessions can't be attached to anyway, so the tmux layer
added complexity with no benefit. State now tracks a PID instead of a
tmux session name; liveness is checked with os.kill(pid, 0).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
unawaited saveAction/deleteAction calls in pushAction could outlive the
test and access the SQLite connection after tearDown closed it, causing
the native FFI layer to hit freed memory (SIGBUS / exit code -7).
Making both DB calls awaited ensures pushAction only returns once the
action is fully persisted, eliminating the race condition.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The new Claude Code trust dialog appeared inside the tmux PTY despite -p
mode and stdout being piped, blocking the agent indefinitely. With
< /dev/null the dialog could never be answered.
Replace < /dev/null with printf '\n' | so the Enter keypress confirms the
default "Yes, I trust this folder" option. After that single newline stdin
reaches EOF, which -p mode ignores.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously claude was launched with -p (print mode) which produces no
visible TUI. Attaching to the session with `tmux attach -t issue-NNN`
showed a blank terminal. Removing -p makes Claude run its interactive
TUI inside the tmux pane, so the session is fully watchable.
Add scripts/test_agent_loop.py covering _start_agent command
construction and state file round-trips.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>