Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a797f9d
fix(git): kill post-merge friction — sync FF-only realign (not rebase…
AIOSAI Jun 11, 2026
1871e55
feat(skills): revive dormant citizen — namespace skills.*→aipass.skil…
AIOSAI Jun 12, 2026
8379f88
feat(commons): revive dormant citizen — verify namespace migration (1…
AIOSAI Jun 12, 2026
ee004c4
feat(daemon): revive dormant citizen (revive-to-working ONLY) — scrub…
AIOSAI Jun 12, 2026
dea91bc
feat(backup): revive dormant citizen (revive-to-working) — path-depth…
AIOSAI Jun 12, 2026
bbe3f43
feat(backup): namespace migration standalone -> aipass.backup.* citiz…
AIOSAI Jun 12, 2026
1f3727d
fix(backup): split top-level --help from bare introspection — drone @…
AIOSAI Jun 12, 2026
1747a23
feat(backup): restore full 9-stage rich CLI output (FPLAN-0263) — new…
AIOSAI Jun 12, 2026
43a6ab2
fix(drone): route @backup through interactive passthrough — add 'back…
AIOSAI Jun 12, 2026
5ab257e
feat(backup): FPLAN-0265 — seedgo 100% + 94-test green foundation (re…
AIOSAI Jun 12, 2026
826ffcd
feat(backup): FPLAN-0266 — snapshot fidelity + shared core (restorati…
AIOSAI Jun 12, 2026
a8cd576
feat(backup): FPLAN-0267 — versioned baseline + per-file diff engine …
AIOSAI Jun 12, 2026
1049bc3
feat(backup): FPLAN-0268 — Google Drive sync pipeline + restore comma…
AIOSAI Jun 12, 2026
ffc5f3b
fix(memory): rollover no longer silently loses rolled-off learnings —…
AIOSAI Jun 13, 2026
9e5ff4e
fix(backup): Google Drive folder duplication + dedup-wipe — restore G…
AIOSAI Jun 13, 2026
c63a43a
docs: de-hardcode branch count in devpulse README + branch prompt (→ …
AIOSAI Jun 13, 2026
f4b7769
fix(backup): .backupignore = true .gitignore via pathspec — single so…
AIOSAI Jun 13, 2026
a001666
chore(backup): track repo-root .backupignore — it is the single sourc…
AIOSAI Jun 13, 2026
c5a96cb
fix(backup): rename store dir .backup_system to .backup + remove dead…
AIOSAI Jun 13, 2026
1057be6
fix(prax): slim devpulse dashboard — drop duplicated todos[] bodies, …
AIOSAI Jun 13, 2026
58bd70b
fix(prax): prune deprecated dashboard sections on refresh (bulletin_b…
AIOSAI Jun 13, 2026
fc49928
fix(prax): slim dashboard to lean glance — drop session/todo/ai_mail …
AIOSAI Jun 13, 2026
37fb07c
fix(prax): quick_status self-sources mail counts from inbox.json (dec…
AIOSAI Jun 13, 2026
d9a2a48
fix(ai_mail): retire dashboard_sync writer — stop writing the ai_mail…
AIOSAI Jun 13, 2026
e47d4f0
feat(memory): FPLAN-0270 Phase 1 — entry_limits config (warn-first, e…
AIOSAI Jun 13, 2026
828cc1c
feat(memory): FPLAN-0270 Phase 2 — check_entry validator + drone @mem…
AIOSAI Jun 13, 2026
7064375
feat(memory): FPLAN-0270 Phase 3 — changed_entries diff helper + writ…
AIOSAI Jun 13, 2026
54dcfb4
feat(hooks): FPLAN-0270 Phase 4 — edit_gate Write-path .trinity char-…
AIOSAI Jun 13, 2026
5336d8f
feat(hooks): FPLAN-0270 Phase 5 — edit_gate Edit/MultiEdit reconstruc…
AIOSAI Jun 13, 2026
a8d05e2
feat(memory): FPLAN-0271 — relocate config to json-home + unify 9 loa…
AIOSAI Jun 13, 2026
7cf319b
feat(memory): DPLAN-0207 P1 — unified entry schema (key_learnings dic…
AIOSAI Jun 13, 2026
2f327d8
fix(memory): DPLAN-0207 P1 hotfix — detector + learnings manager list…
AIOSAI Jun 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
30 changes: 30 additions & 0 deletions .backupignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Backup System ignore patterns (gitignore-style)
# Lines starting with # are comments. Blank lines are ignored.
# Edit this file to customize. Source defaults: handlers/ignore/patterns.py

.backup_system/
.backup/
.git/
.svn/
.hg/
__pycache__/
.pytest_cache/
*.pyc
*.pyo
*.egg-info/
.venv/
venv/
.tox/
node_modules/
.vscode/
.idea/
*.swp
*.swo
.DS_Store
Thumbs.db
build/
dist/
*.log
.ruff_cache/
.coverage
*logs
8 changes: 5 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ User: user

# Startup protocol

On any greeting, silently read these files from CWD and run the commands — no narration, no announcing steps. Just do it and respond with the status.
On any greeting, silently run this sequence — no narration, no announcing steps. Just do it and respond with the status.

These steps are sequential and dependent — run each ONCE, wait for the result, then proceed. Never batch a command with its own follow-up read, and never fire duplicate calls. If output looks blank, wait — don't retry.

- Read: `.trinity/passport.json`, `.trinity/local.json`, `.trinity/observations.json`, `README.md`
- Check: `drone @ai_mail inbox` — process any mail, don't ask.
- Run: `drone @git status`
- Refresh: `drone @prax dashboard refresh @<self>` — where `<self>` is your branch name (CWD directory name)
- Dashboard: Read `DASHBOARD.local.json` — act on what needs attention (new mail → check inbox, active plans → note them). This is your single status glance.

Use drone commands for all operations. Never raw git, gh, file access, or python -m when drone provides it.

Expand Down
231 changes: 231 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

15 changes: 0 additions & 15 deletions src/aipass/ai_mail/.seedgo/bypass.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,6 @@
"standard": "deep_nesting",
"reason": "2 functions: get_user_by_email() depth 4, get_all_users() depth 4 — registry lookup with path normalization and validation"
},
{
"file": "apps/handlers/email/dashboard_sync.py",
"standard": "handlers",
"reason": "Imports prax.apps.modules.dashboard.write_section — cross-branch module import required for dashboard integration. No ai_mail module wraps this."
},
{
"file": "apps/handlers/email/delivery.py",
"standard": "handlers",
Expand Down Expand Up @@ -120,11 +115,6 @@
"standard": "naming",
"reason": "False positive — _append_footer is a function reference stored in a local variable, not a module-level constant."
},
{
"file": "apps/handlers/email/dashboard_sync.py",
"standard": "naming",
"reason": "False positive — _write_section is a lazy-import function reference, not a module-level constant."
},
{
"file": "apps/handlers/email/delivery.py",
"standard": "naming",
Expand Down Expand Up @@ -200,11 +190,6 @@
"standard": "deep_nesting",
"reason": "_send_direct() depth 5 (arg parsing with branch resolution, --from flag, --dispatch flag), handle_close() depth 4 (close with archive + dashboard update)"
},
{
"file": "apps/handlers/email/dashboard_sync.py",
"standard": "deep_nesting",
"reason": "_human_readable_age() depth 5, _calculate_section_data() depth 5 — timestamp parsing with multiple fallback formats"
},
{
"file": "apps/handlers/dispatch/dispatch_monitor.py",
"standard": "deep_nesting",
Expand Down
9 changes: 1 addition & 8 deletions src/aipass/ai_mail/apps/handlers/email/close_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,24 +56,17 @@ def batch_close(

def batch_close_post_ops(
branch_path: Path,
push_dashboard_fn: Optional[Callable] = None,
update_central_fn: Optional[Callable] = None,
purge_deleted_fn: Optional[Callable] = None,
) -> None:
"""
Run post-operations after a batch close (dashboard update + purge).
Run post-operations after a batch close (central update + purge).

Args:
branch_path: Path to branch directory
push_dashboard_fn: Optional push_dashboard_update callable
update_central_fn: Optional update_central callable
purge_deleted_fn: Optional purge_deleted_folder callable
"""
if push_dashboard_fn:
try:
push_dashboard_fn(branch_path)
except Exception as e:
logger.warning("[close] push_dashboard_fn failed for %s: %s", branch_path, e)
if update_central_fn:
try:
update_central_fn()
Expand Down
9 changes: 1 addition & 8 deletions src/aipass/ai_mail/apps/handlers/email/error_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,25 +93,18 @@ def on_email_delivered(
new_count: int,
opened_count: int,
total: int,
push_dashboard_fn: Optional[Callable] = None,
update_central_fn: Optional[Callable] = None,
) -> None:
"""
Post-delivery callback: update dashboard and central.
Post-delivery callback: update central.

Args:
branch_path: Path to the branch that received email
new_count: Number of new (unread) messages
opened_count: Number of opened messages
total: Total message count
push_dashboard_fn: Callable for push_dashboard_update
update_central_fn: Callable for update_central
"""
if push_dashboard_fn:
try:
push_dashboard_fn(branch_path)
except Exception as e:
logger.warning("[error_dispatch] dashboard update failed for %s: %s", branch_path, e)
if update_central_fn:
try:
update_central_fn()
Expand Down
15 changes: 1 addition & 14 deletions src/aipass/ai_mail/apps/handlers/email/inbox_cleanup.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,6 @@ def _get_inbox_lock():
return _inbox_lock


def _get_push_dashboard_update() -> Any:
"""Lazy import push_dashboard_update from dashboard_sync."""
from aipass.ai_mail.apps.handlers.email.dashboard_sync import push_dashboard_update

return push_dashboard_update


def _get_update_central() -> Any:
"""Lazy import update_central."""
from aipass.ai_mail.apps.handlers.central_writer import update_central
Expand Down Expand Up @@ -191,13 +184,7 @@ def mark_all_read_and_archive(branch_path: Path) -> Tuple[bool, str, int]:


def _update_dashboard(branch_path: Path, new: int, opened: int, total: int) -> None:
"""Update dashboard ai_mail section with enriched data via write-through API."""
try:
_get_push_dashboard_update()(branch_path)
except Exception as e:
logger.warning("[cleanup] dashboard update failed for %s: %s", branch_path, e)

# Update central after any inbox changes
"""Update central stats after inbox changes."""
try:
_get_update_central()()
except Exception as e:
Expand Down
2 changes: 0 additions & 2 deletions src/aipass/ai_mail/apps/modules/dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,6 @@ def _orchestrate_dispatch_send(args: List[str]) -> bool:
from aipass.ai_mail.apps.handlers.email.delivery import deliver_email_to_branch
from aipass.ai_mail.apps.handlers.email.header import prepend_dispatch_header
from aipass.ai_mail.apps.handlers.email.error_dispatch import dispatch_send_error, on_email_delivered
from aipass.ai_mail.apps.handlers.email.dashboard_sync import push_dashboard_update
from aipass.ai_mail.apps.handlers.users.user import get_current_user
from aipass.ai_mail.apps.handlers.registry.read import get_branch_by_email

Expand All @@ -286,7 +285,6 @@ def _delivery_callback(branch_path, new_count, opened_count, total):
new_count,
opened_count,
total,
push_dashboard_fn=push_dashboard_update,
update_central_fn=update_central,
)

Expand Down
13 changes: 4 additions & 9 deletions src/aipass/ai_mail/apps/modules/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,8 @@
from pathlib import Path
from typing import List

# Infrastructure
_AI_MAIL_DIR = Path(__file__).resolve().parents[2]
_REPO_ROOT = _AI_MAIL_DIR.parents[2]

from aipass.prax import logger
from aipass.cli.apps.modules import console, error

# Handlers - business logic providers
from aipass.ai_mail.apps.handlers.email.dashboard_sync import push_dashboard_update
from aipass.ai_mail.apps.handlers.email.create import load_email_file
from aipass.ai_mail.apps.handlers.email.format import format_email_list_item, format_email_header
from aipass.ai_mail.apps.handlers.email.inbox_ops import load_inbox
Expand All @@ -48,6 +41,9 @@
from aipass.ai_mail.apps.handlers.email.inbox_resolve import resolve_inbox_target
from aipass.ai_mail.apps.modules.email_send import handle_send

_AI_MAIL_DIR = Path(__file__).resolve().parents[2]
_REPO_ROOT = _AI_MAIL_DIR.parents[2]

try:
from aipass.ai_mail.apps.handlers.central_writer import update_central
except ImportError as e:
Expand Down Expand Up @@ -255,7 +251,7 @@ def handle_close(args: List[str]) -> bool:
except ImportError as e:
logger.warning("[email] purge import unavailable: %s", e)
run_purge = None
batch_close_post_ops(branch_path, push_dashboard_update, update_central, run_purge)
batch_close_post_ops(branch_path, update_central, run_purge)
console.print(f"\nClosed {closed}, failed {failed}")
return True
except Exception as e:
Expand Down Expand Up @@ -387,7 +383,6 @@ def print_introspection():
console.print(" - reply.py (get_email_by_id — retrieve email by message ID)")
console.print(" - reply.py (send_reply — send reply to an email)")
console.print(" - header.py (prepend_dispatch_header — prepend dispatch header to message)")
console.print(" - dashboard_sync.py (push_dashboard_update — push email stats to dashboard)")
console.print(" - error_dispatch.py (dispatch_send_error — handle and report send errors)")
console.print(" - error_dispatch.py (on_email_delivered — post-delivery callback handler)")
console.print(" handlers/users/")
Expand Down
8 changes: 3 additions & 5 deletions src/aipass/ai_mail/apps/modules/email_send.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,10 @@
from pathlib import Path
from typing import List

_AI_MAIL_DIR = Path(__file__).resolve().parents[2]
_REPO_ROOT = _AI_MAIL_DIR.parents[2]

from aipass.prax import logger
from aipass.cli.apps.modules import console, error
from aipass.trigger.apps.modules.core import trigger

from aipass.ai_mail.apps.handlers.email.dashboard_sync import push_dashboard_update
from aipass.ai_mail.apps.handlers.email.delivery import deliver_email_to_branch
from aipass.ai_mail.apps.handlers.email.create import create_email_file, load_email_file
from aipass.ai_mail.apps.handlers.email.header import prepend_dispatch_header
Expand All @@ -40,6 +36,9 @@
from aipass.ai_mail.apps.handlers.email.error_dispatch import dispatch_send_error, on_email_delivered
from aipass.ai_mail.apps.handlers.email.send_args import parse_send_args, resolve_dispatch_target

_AI_MAIL_DIR = Path(__file__).resolve().parents[2]
_REPO_ROOT = _AI_MAIL_DIR.parents[2]

try:
from aipass.ai_mail.apps.handlers.central_writer import update_central
except ImportError as e:
Expand All @@ -54,7 +53,6 @@ def _delivery_callback(branch_path, new_count, opened_count, total):
new_count,
opened_count,
total,
push_dashboard_fn=push_dashboard_update,
update_central_fn=update_central,
)

Expand Down
31 changes: 5 additions & 26 deletions src/aipass/ai_mail/tests/test_close_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,11 @@ def test_batch_close_post_ops_all_fns_called(tmp_path: Path):
branch_path = tmp_path / "branch"
branch_path.mkdir()

push_fn = MagicMock()
central_fn = MagicMock()
purge_fn = MagicMock()

mod.batch_close_post_ops(branch_path, push_fn, central_fn, purge_fn)
mod.batch_close_post_ops(branch_path, central_fn, purge_fn)

push_fn.assert_called_once_with(branch_path)
central_fn.assert_called_once_with()
purge_fn.assert_called_once_with(branch_path / ".ai_mail.local")

Expand All @@ -137,36 +135,19 @@ def test_batch_close_post_ops_none_fns(tmp_path: Path):
branch_path.mkdir()

# Should not raise
mod.batch_close_post_ops(branch_path, None, None, None)


def test_batch_close_post_ops_push_exception_suppressed(tmp_path: Path):
"""Exception in push_dashboard_fn is caught; other fns still called."""
branch_path = tmp_path / "branch"
branch_path.mkdir()

push_fn = MagicMock(side_effect=RuntimeError("push failed"))
central_fn = MagicMock()
purge_fn = MagicMock()

mod.batch_close_post_ops(branch_path, push_fn, central_fn, purge_fn)

central_fn.assert_called_once()
purge_fn.assert_called_once()
mod.batch_close_post_ops(branch_path, None, None)


def test_batch_close_post_ops_central_exception_suppressed(tmp_path: Path):
"""Exception in update_central_fn is caught; purge still called."""
branch_path = tmp_path / "branch"
branch_path.mkdir()

push_fn = MagicMock()
central_fn = MagicMock(side_effect=RuntimeError("central failed"))
purge_fn = MagicMock()

mod.batch_close_post_ops(branch_path, push_fn, central_fn, purge_fn)
mod.batch_close_post_ops(branch_path, central_fn, purge_fn)

push_fn.assert_called_once()
purge_fn.assert_called_once()


Expand All @@ -175,13 +156,11 @@ def test_batch_close_post_ops_purge_exception_suppressed(tmp_path: Path):
branch_path = tmp_path / "branch"
branch_path.mkdir()

push_fn = MagicMock()
central_fn = MagicMock()
purge_fn = MagicMock(side_effect=RuntimeError("purge failed"))

mod.batch_close_post_ops(branch_path, push_fn, central_fn, purge_fn)
mod.batch_close_post_ops(branch_path, central_fn, purge_fn)

push_fn.assert_called_once()
central_fn.assert_called_once()


Expand All @@ -192,6 +171,6 @@ def test_batch_close_post_ops_partial_fns(tmp_path: Path):

central_fn = MagicMock()

mod.batch_close_post_ops(branch_path, None, central_fn, None)
mod.batch_close_post_ops(branch_path, central_fn, None)

central_fn.assert_called_once_with()
2 changes: 0 additions & 2 deletions src/aipass/ai_mail/tests/test_dispatch_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ def _silence_json_handler():
_H_DELIVERY = "aipass.ai_mail.apps.handlers.email.delivery"
_H_HEADER = "aipass.ai_mail.apps.handlers.email.header"
_H_ERR = "aipass.ai_mail.apps.handlers.email.error_dispatch"
_H_DASH = "aipass.ai_mail.apps.handlers.email.dashboard_sync"
_H_USERS = "aipass.ai_mail.apps.handlers.users.user"
_H_REG = "aipass.ai_mail.apps.handlers.registry.read"
_H_CENTRAL = "aipass.ai_mail.apps.handlers.central_writer"
Expand Down Expand Up @@ -658,7 +657,6 @@ def _send_patches(overrides: dict | None = None) -> ExitStack:
f"{_H_HEADER}.prepend_dispatch_header": MagicMock(return_value="[DISPATCH] Body"),
f"{_H_SEND}.send_to_single": MagicMock(return_value=(True, None)),
f"{_H_ERR}.on_email_delivered": MagicMock(),
f"{_H_DASH}.push_dashboard_update": MagicMock(),
f"{_H_USERS}.get_current_user": MagicMock(return_value={"name": "test"}),
f"{_H_REG}.get_branch_by_email": MagicMock(return_value={"email": "@target"}),
f"{_H_CENTRAL}.update_central": MagicMock(),
Expand Down
Loading
Loading