From ac7b04e11d848c617aad2602be3fa5f3e90ed6fa Mon Sep 17 00:00:00 2001 From: AIPass Date: Sun, 5 Apr 2026 22:55:35 -0700 Subject: [PATCH] feat(system): AGENTS.md/GEMINI.md startup instructions + prax Codex branch detection from CWD (#192) Co-authored-by: @devpulse --- AGENTS.md | 12 ++- GEMINI.md | 12 ++- .../handlers/monitoring/filesystem_handler.py | 86 ++++++++++++++++++- 3 files changed, 106 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 11f63545..f3a005c0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,9 +28,19 @@ drone @ai_mail inbox # Check email drone @ai_mail email @target "Subject" "Body" # Send email drone @flow list open # Active plans +## Startup + +On any greeting or first message, silently read these files and respond with status: +- `.trinity/passport.json` — your identity, role, purpose, principles +- `.trinity/local.json` — session history and key learnings +- `STATUS.local.md` — current work, issues, todos +- Check if `.ai_mail.local/inbox.json` exists — if so, read it and process any mail + +Your identity and branch context are also injected via hooks on session start and every prompt. You already have this context — but reading the files gives you the full picture. + ## Identity -Read .trinity/passport.json to understand your role. Read .trinity/local.json for session history. Read STATUS.local.md for current work. +You are a citizen of AIPass. Your `.trinity/passport.json` defines who you are. Read it first — before writing anything, before making decisions. Your role, purpose, and principles are in that file. ## Key Principles diff --git a/GEMINI.md b/GEMINI.md index 11f63545..f3a005c0 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -28,9 +28,19 @@ drone @ai_mail inbox # Check email drone @ai_mail email @target "Subject" "Body" # Send email drone @flow list open # Active plans +## Startup + +On any greeting or first message, silently read these files and respond with status: +- `.trinity/passport.json` — your identity, role, purpose, principles +- `.trinity/local.json` — session history and key learnings +- `STATUS.local.md` — current work, issues, todos +- Check if `.ai_mail.local/inbox.json` exists — if so, read it and process any mail + +Your identity and branch context are also injected via hooks on session start and every prompt. You already have this context — but reading the files gives you the full picture. + ## Identity -Read .trinity/passport.json to understand your role. Read .trinity/local.json for session history. Read STATUS.local.md for current work. +You are a citizen of AIPass. Your `.trinity/passport.json` defines who you are. Read it first — before writing anything, before making decisions. Your role, purpose, and principles are in that file. ## Key Principles diff --git a/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py b/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py index bffd2ce1..f858172a 100644 --- a/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py +++ b/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py @@ -72,6 +72,10 @@ class MonitoringFileHandler(FileSystemEventHandler): self._last_agent_action: Dict[str, str] = {} # Track model per session file for display tags self._session_models: Dict[str, str] = {} + # Track detected branch per CLI session file (from session metadata) + self._session_branches: Dict[str, str] = {} + # Track files we've already warned about for branch detection + self._branch_warn_logged: set = set() # -- public property so the module can swap queues after construction ------ @property @@ -191,6 +195,84 @@ class MonitoringFileHandler(FileSystemEventHandler): return None + # ========================================================================= + # CLI SESSION BRANCH DETECTION + # ========================================================================= + + @staticmethod + def _branch_from_cwd(cwd: str) -> Optional[str]: + """Extract branch name from a CWD path like /path/to/src/aipass/devpulse.""" + if not cwd: + return None + parts = Path(cwd).parts + if 'aipass' in parts: + idx = parts.index('aipass') + if idx + 1 < len(parts): + return parts[idx + 1].upper() + # Fallback: check src/{name} for branches outside aipass namespace + if 'src' in parts: + idx = parts.index('src') + if idx + 1 < len(parts): + return parts[idx + 1].upper() + return None + + def _get_codex_branch(self, file_path, path_key: str) -> str: + """Get branch for a Codex session, reading session_meta if needed.""" + if path_key in self._session_branches: + return self._session_branches[path_key] + + # Read first line of JSONL for session_meta + try: + with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: + first_line = f.readline().strip() + if first_line: + meta = _json.loads(first_line) + if meta.get('type') == 'session_meta': + cwd = meta.get('payload', {}).get('cwd', '') + branch = self._branch_from_cwd(cwd) + if branch: + self._session_branches[path_key] = branch + return branch + except (OSError, _json.JSONDecodeError) as e: + if path_key not in self._branch_warn_logged: + logger.info(f"[monitor] Could not read Codex session_meta: {e}") + self._branch_warn_logged.add(path_key) + + self._session_branches[path_key] = 'CODEX' + return 'CODEX' + + def _get_gemini_branch(self, file_path, path_key: str) -> str: + """Get branch for a Gemini session from project slug in path.""" + if path_key in self._session_branches: + return self._session_branches[path_key] + + # Path format: ~/.gemini/tmp//chats/session-*.json + # Project slug maps to a directory via ~/.gemini/projects.json + parts = Path(file_path).parts + if 'tmp' in parts: + idx = parts.index('tmp') + if idx + 1 < len(parts): + slug = parts[idx + 1] + # Try to resolve slug to real branch via projects.json + projects_file = Path.home() / '.gemini' / 'projects.json' + if projects_file.exists(): + try: + data = _json.loads(projects_file.read_text()) + for project_path, project_slug in data.get('projects', {}).items(): + if project_slug == slug: + branch = self._branch_from_cwd(project_path) + if branch: + self._session_branches[path_key] = branch + return branch + except (OSError, _json.JSONDecodeError): + pass + # Fallback: use slug as branch name + self._session_branches[path_key] = slug.upper() + return slug.upper() + + self._session_branches[path_key] = 'GEMINI' + return 'GEMINI' + # ========================================================================= # MODEL TAG HELPERS # ========================================================================= @@ -542,13 +624,13 @@ class MonitoringFileHandler(FileSystemEventHandler): # Codex JSONL files: parse agent activity if file_path.suffix == '.jsonl' and '.codex/sessions/' in path_str: - codex_branch = 'CODEX' + codex_branch = self._get_codex_branch(file_path, path_str) if self._parse_codex_activity(file_path, codex_branch): return # Gemini JSON session files: parse agent activity if file_path.suffix == '.json' and '.gemini/tmp/' in path_str and '/chats/' in path_str: - gemini_branch = 'GEMINI' + gemini_branch = self._get_gemini_branch(file_path, path_str) if self._parse_gemini_activity(file_path, gemini_branch): return