refactor(status): decommission entire STATUS flow — STATUS.local.md + central STATUS.md retired, replaced by local.json todos[] + dashboard count (TDPLAN-0007)

Cross-branch coordinated change. Per-branch STATUS.local.md (13) + central
STATUS.md deleted; all prompts/docs/skills/hooks/footer/scaffolding scrubbed.
Status-sync engine kept intact-but-inert (trigger registry unwired, 3 lines).
Replacement: operational todos[] in .trinity/local.json (@memory schema, capped,
rollover-exempt), pushed to all 13 branches + surfaced as dashboard todo_count.

Owners: memory (schema+global push+template), prax (dashboard todo_section +
engine dormant), trigger (unwire), aipass (init scaffolding), spawn (template
delete + todos[] seed), hooks (compact recovery), ai_mail (footer), seedgo
(_RUNTIME_ARTIFACTS cleanup), devpulse (scrub+delete+assemble).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
AIOSAI
2026-06-07 21:53:24 -07:00
co-authored by Claude Opus 4.8
parent f4b203a74e
commit 626ab81a46
48 changed files with 413 additions and 163 deletions
+1 -1
View File
@@ -17,7 +17,7 @@ Goal: signal density over prose. Prompts are injected every turn — every line
# What NOT to put in a prompt
- Session state, current work, in-flight issues. That goes in `STATUS.local.md` and `.trinity/local.json`.
- Session state, current work, in-flight issues. That goes in `.trinity/local.json` (todos[]) and `DASHBOARD.local.json`.
- Long explanations of how a system works. Plant a breadcrumb ("see `@branch --help`") and move on.
- Personal notes ("remember, you like short replies"). That goes in `.trinity/observations.json`.
- Version numbers, PR numbers, dates. Those rot within days.
+7 -10
View File
@@ -11,7 +11,7 @@ Patterns here are exact. Don't guess command syntax — examples are the API. Mi
# AIPL — Terse Writing Convention
When writing .trinity/, ai_mail, STATUS.local.md, plans: use AIPL. Human-facing output (CLI, logs, README): use English.
When writing .trinity/, ai_mail, plans: use AIPL. Human-facing output (CLI, logs, README): use English.
Rules:
- Drop grammar: the, a, an, for, with, on, in, at, to, from, of, by, and, but, or, was, were, been
@@ -151,19 +151,16 @@ Never create plan files manually. Always `drone @flow create`. Flow handles numb
`.trinity/` files are your memories — experiential, personal, yours. How you persist across sessions.
`STATUS.local.md` is different — live status beacon for ecosystem. Auto-synced to central `STATUS.md` on PR create/merge. Other agents read STATUS to see your state without digging into memories. Crossover with `local.json` fine — same fact, different purpose: `local.json` for you, `STATUS.local.md` for ecosystem.
Four files:
Three files:
- `passport.json` — IDENTITY. Role, purpose, principles. Update only when identity genuinely evolves.
- `local.json` — YOUR MEMORY. Session log (`sessions[]`) + `key_learnings`. What happened, what learned, what matters next.
- `local.json` — YOUR MEMORY. Session log (`sessions[]`) + `key_learnings` + `todos[]`. What happened, what learned, what matters next.
- `observations.json` — MEMORY OF THE USER. Preferences, style, friction, breakthroughs. Skip if nothing new this session.
- `STATUS.local.md` — PUBLIC BEACON. Current work, issues, todos, recently completed. Notepad for quick captures.
Where to put what:
- "Worked on DPLAN-0125, learned about peak hours" → `local.json`
- "User prefers short replies" → `observations.json`
- "PR #266 needs merge, Track G blocked" → `STATUS.local.md`
- "Fix drone help formatting" as reminder → `STATUS.local.md` Notepad
- "PR #266 needs merge, Track G blocked" → `local.json` todos[]
- "Fix drone help formatting" as reminder → `local.json` todos[]
- "Role shifted from builder to orchestrator" → `passport.json`
Save proactively. Triggers: after milestone, decision, learning, before switching topics.
@@ -195,7 +192,7 @@ Use sub-agents for:
Do it yourself only when:
- User explicitly asks you to read or look at something
- Tiny edits — fix a typo, update a memory file, small config change
- Writing memories, STATUS, plan updates (your own files)
- Writing memories, plan updates (your own files)
- Quick one-line commands — drone status, inbox check
How to use them:
@@ -247,7 +244,7 @@ Small knowledge traces trigger awareness. Not full knowledge — enough to know
Prompts: plant breadcrumbs, not encyclopedias. Two lines ("this exists, look here") beat twenty explaining how.
Prompts are signposts, not journals. Injected every turn — keep minimal. Never track state/sessions/context in prompts. State → `.trinity/` + `STATUS.local.md`. Prompts guide; memories record; registries catalog.
Prompts are signposts, not journals. Injected every turn — keep minimal. Never track state/sessions/context in prompts. State → `.trinity/` + `DASHBOARD.local.json`. Prompts guide; memories record; registries catalog.
If `drone` can't find the AIPass registry, set `AIPASS_HOME=/path/to/AIPass` in shell profile and `~/.claude/settings.json` env block.
+1 -1
View File
@@ -6,7 +6,7 @@ Agent workspace powered by AIPass.
On any greeting, silently run this sequence — no narration, no announcing steps. Just do it and respond with the status.
- Read: `.trinity/passport.json`, `.trinity/local.json`, `.trinity/observations.json`, `README.md`, `STATUS.local.md`
- Read: `.trinity/passport.json`, `.trinity/local.json`, `.trinity/observations.json`, `README.md`
Use drone commands for all operations. Never raw git, gh, file access, or python -m when drone provides it.
+2 -4
View File
@@ -14,9 +14,8 @@ Purpose: Button up everything at the end of a session — or before a /compact.
Each memory file plays a distinct role. Update based on what actually changed this session.
- **`.trinity/passport.json`** — IDENTITY. Who you are: role, capabilities, principles. Only update if identity genuinely evolved this session.
- **`.trinity/local.json`** — YOUR MEMORY. Add/update session entry with a summary of work done. Add key_learnings for anything learned. Trim oldest sessions if over 20.
- **`.trinity/local.json`** — YOUR MEMORY. Add/update session entry with a summary of work done. Add key_learnings for anything learned. Update todos[] with current in-flight items. Trim oldest sessions if over 20.
- **`.trinity/observations.json`** — YOUR MEMORY OF THE USER. Collaboration insights, preferences, friction points. Skip if nothing new about the user this session.
- **`STATUS.local.md`** — PUBLIC STATUS BEACON. Current work, known issues, todos, notepad. Auto-synced to central STATUS.md on PR events — this is how other branches see you. Keep Current Work accurate.
## 2. Active Plans
@@ -38,7 +37,7 @@ Each memory file plays a distinct role. Update based on what actually changed th
## 5. Loose Ends
- Flag anything in-flight: running background agents, dispatched branches waiting for replies, pending decisions
- If anything can't survive compaction (e.g., agent IDs needed for resume), write it to STATUS.local.md Notepad
- If anything can't survive compaction (e.g., agent IDs needed for resume), write it to local.json todos[]
## Confirm
@@ -47,7 +46,6 @@ List everything updated. Format:
Prep complete:
- local.json: [what was added]
- observations.json: [updated / skipped]
- STATUS.local.md: [updated / skipped]
- Plans: [which ones updated]
- Git: [branch, uncommitted count, suggestion]
- Inbox: [count, action taken]
+1 -2
View File
@@ -14,9 +14,8 @@ Purpose: Update branch memory files after completing work this session.
Each memory file plays a distinct role. Update based on what actually changed this session.
- **`.trinity/passport.json`** — IDENTITY. Who you are: role, capabilities, principles. Only update if identity genuinely evolved this session. Don't touch it just to touch it.
- **`.trinity/local.json`** — YOUR MEMORY. Session history and key_learnings. Add a session entry for significant work. Add key_learnings for facts you'd need next time. Trim oldest sessions if over 20.
- **`.trinity/local.json`** — YOUR MEMORY. Session history, key_learnings, and todos[]. Add a session entry for significant work. Add key_learnings for facts you'd need next time. Update todos[] with open items. Trim oldest sessions if over 20.
- **`.trinity/observations.json`** — YOUR MEMORY OF THE USER. Collaboration insights, preferences, friction points, flow states. Skip entirely if nothing new about the user this session.
- **`STATUS.local.md`** — PUBLIC STATUS BEACON. Current work, known issues, todos, notepad. Auto-synced to central STATUS.md on PR events — this is how other branches see you. Keep Current Work accurate and drop quick notes in the Notepad section.
## If Relevant
-1
View File
@@ -25,4 +25,3 @@ Purpose: Update branch memory files after completing work this session.
- **.trinity/passport.json** — Evolve identity when the branch's role, capabilities, or principles have genuinely changed. Don't update just to update — but don't leave placeholders forever either.
- **README.md** — Does it reflect current state? Update if stale.
- **STATUS.local.md** — Drop quick notes on issues, todos, or ideas in the Notepad section.
+1 -1
View File
@@ -38,7 +38,7 @@ Purpose: Button up everything at the end of a session — or before a /compact.
## 5. Loose Ends
- Flag anything in-flight: running background agents, dispatched branches waiting for replies, pending decisions
- If anything can't survive compaction, write it to STATUS.local.md Notepad
- If anything can't survive compaction, write it to local.json todos[]
## Confirm
-1
View File
@@ -110,7 +110,6 @@ src/aipass/*/apps/integrations/**
!src/aipass/spawn/templates/builder/docs.local/
!src/aipass/spawn/templates/builder/docs.local/**
!src/aipass/spawn/templates/builder/DASHBOARD.local.json
!src/aipass/spawn/templates/builder/STATUS.local.md
# CI artifacts
windows-pytest-results/
+1 -1
View File
@@ -8,7 +8,7 @@ User: user
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.
- Read: `.trinity/passport.json`, `.trinity/local.json`, `.trinity/observations.json`, `README.md`, `STATUS.local.md`
- 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`
+18
View File
@@ -10,6 +10,24 @@ and this project uses [Calendar Versioning](https://calver.org/) in the format
## [2026.W23] - 2026-06-02
### Removed
- **The entire STATUS flow is decommissioned (TDPLAN-0007).** The per-branch
hand-maintained `STATUS.local.md` beacon and the auto-aggregated central
`STATUS.md` (853 lines / 70 KB nobody read) are gone — deleted from disk
across all 13 branches and scrubbed from every prompt, doc, startup protocol,
`/prep` + `/memo` skill, the compact-recovery hook, the email footer, and
`aipass init` / spawn scaffolding. Live branch state was already fully covered
by `DASHBOARD.local.json` (prax) and history by `.trinity/local.json`. The
status-sync engine is kept **intact but inert** — made dormant by unwiring its
3-line trigger registration (`trigger registry.py`), so the code stays
revivable. The one thing STATUS uniquely gave us — a quick scratch todo — is
replaced by an operational `todos[]` section in `.trinity/local.json`
(@memory-owned schema, capped, never vectorized by rollover), pushed to all 13
branches and surfaced as a `todo_count` on the dashboard. Shipped as one
coordinated cross-branch change (memory, prax, trigger, aipass, spawn, hooks,
ai_mail, seedgo + devpulse).
### Changed
- **All 13 branches at seedgo 100% under the new introspection standard.**
+1 -2
View File
@@ -10,10 +10,9 @@ On any greeting, silently run this sequence — no narration, no announcing step
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`, `STATUS.local.md`
- Read: `.trinity/passport.json`, `.trinity/local.json`, `.trinity/observations.json`, `README.md`
- 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.
- Refresh: If `STATUS.local.md` is stale (last updated date older than latest session in local.json), update it from your memories. Keep Current Work accurate.
Use drone commands for all operations. Never raw git, gh, file access, or python -m when drone provides it.
+1 -1
View File
@@ -264,7 +264,7 @@ AIPass stores everything locally in your project directory. To remove it:
```bash
# Remove AIPass files from your project
rm -rf .aipass/ .claude/ .ai_mail.local/ hooks/ src/
rm -f CLAUDE.md AGENTS.md STATUS.local.md *_REGISTRY.json .gitignore
rm -f CLAUDE.md AGENTS.md *_REGISTRY.json .gitignore
# If you installed via pip
pip uninstall aipass
@@ -22,7 +22,6 @@ STANDARD_FOOTER = """
⚠️ TASK CHECKLIST (before marking complete):
□ SEEDGO CHECK → drone @seedgo audit @branch (80%+)
□ UPDATE MEMORIES → Your .trinity/local.json records this work
□ UPDATE STATUS → Your STATUS.local.md reflects current state
□ CLOSE FPLAN → drone @flow close <plan_id>
□ EMAIL SENDER → drone @ai_mail email @<sender> "Subject" "Summary"
@@ -1,6 +1,6 @@
# AIPASS — Branch Prompt
*Injected every turn. Breadcrumbs only — details: README, --help, .trinity/ memories, STATUS.local.md.*
*Injected every turn. Breadcrumbs only — details: README, --help, .trinity/ memories.*
## Identity
@@ -15,11 +15,10 @@ Business logic for `aipass init`. Creates the project scaffold:
3. CLAUDE.md — project prompt (Claude Code reads this)
4. AGENTS.md — Codex equivalent of CLAUDE.md
5. README.md — getting started guide
6. STATUS.local.md — project status
7. .gitignore — standard AIPass ignores
8. .claude/settings.json — Claude Code hooks configuration
9. src/ — directory where agents live
10. .ai_mail.local/inbox.json — empty project mailbox
6. .gitignore — standard AIPass ignores
7. .claude/settings.json — Claude Code hooks configuration
8. src/ — directory where agents live
9. .ai_mail.local/inbox.json — empty project mailbox
Projects are NOT citizens — no .trinity/ directory. Identity lives in the
registry JSON. Init is re-runnable: existing files are skipped, not errors.
@@ -364,16 +363,7 @@ def init_project(target: Path, project_name: str | None = None) -> dict:
readme_md_path.write_text(readme_content, encoding="utf-8")
created.append(str(readme_md_path))
# 7. STATUS.local.md
status_md_path = target / "STATUS.local.md"
if not status_md_path.exists():
status_md_path.write_text(
f"# {name}\n\n**State:** New\n**Last update:** {today}\n\n## Current Work\n\n## Known Issues\n- None\n",
encoding="utf-8",
)
created.append(str(status_md_path))
# 8. .gitignore
# 7. .gitignore
gitignore_path = target / ".gitignore"
if not gitignore_path.exists():
gitignore_path.write_text(sc.gitignore(), encoding="utf-8")
@@ -450,7 +440,7 @@ def update_project(target: Path) -> dict:
"""Update managed scaffold files in an existing AIPass project.
Overwrites managed prompt and config files with the latest templates while
leaving all user-owned files (registry, README, STATUS.local.md, .gitignore,
leaving all user-owned files (registry, README, .gitignore,
src/) untouched.
Args:
@@ -586,7 +576,6 @@ def update_project(target: Path) -> dict:
for skip_name in (
str(registry_path),
str(target / "README.md"),
str(target / "STATUS.local.md"),
str(target / ".gitignore"),
):
skipped.append(skip_name)
@@ -40,8 +40,8 @@ def readme_md(name: str) -> str:
"cd src/my_agent/\n"
"claude # or your preferred AI CLI\n"
"\n"
"# 3. Check project status\n"
"cat STATUS.local.md\n"
"# 3. Check project health\n"
"drone @seedgo audit .\n"
"```\n"
"\n"
"## Project Structure\n"
@@ -52,7 +52,6 @@ def readme_md(name: str) -> str:
" .aipass/ # Prompts (injected per-turn)\n"
" CLAUDE.md # Claude Code instructions\n"
" AGENTS.md # Codex instructions\n"
" STATUS.local.md # Project status\n"
" src/ # Agent directories live here\n"
" <agent_name>/ # Created via aipass init agent\n"
"```\n"
@@ -212,7 +211,6 @@ def gitignore() -> str:
".trinity/\n"
".ai_mail.local/\n"
"*.local.*\n"
"!STATUS.local.md\n"
"\n"
"# Plans (local working docs)\n"
"DPLAN-*\n"
@@ -279,9 +277,6 @@ def prep_md() -> str:
"- **`.trinity/observations.json`** — YOUR MEMORY OF THE USER. "
"Collaboration insights, preferences, friction points. Skip if nothing "
"new about the user this session.\n"
"- **`STATUS.local.md`** — PUBLIC STATUS BEACON. Current work, known "
"issues, todos, notepad. Auto-synced to central STATUS.md on PR events "
"— this is how other branches see you. Keep Current Work accurate.\n"
"\n"
"## 2. Active Plans\n"
"\n"
@@ -308,7 +303,7 @@ def prep_md() -> str:
"- Flag anything in-flight: running background agents, dispatched "
"branches waiting for replies, pending decisions\n"
"- If anything can't survive compaction (e.g., agent IDs needed for "
"resume), write it to STATUS.local.md Notepad\n"
"resume), write it to local.json key_learnings\n"
"\n"
"## Confirm\n"
"\n"
@@ -317,7 +312,6 @@ def prep_md() -> str:
"Prep complete:\n"
"- local.json: [what was added]\n"
"- observations.json: [updated / skipped]\n"
"- STATUS.local.md: [updated / skipped]\n"
"- Plans: [which ones updated]\n"
"- Git: [branch, uncommitted count, suggestion]\n"
"- Inbox: [count, action taken]\n"
+5 -10
View File
@@ -102,7 +102,6 @@ def test_init_project_creates_all_expected_files(tmp_path):
target / "CLAUDE.md",
target / "AGENTS.md",
target / "README.md",
target / "STATUS.local.md",
target / ".gitignore",
target / ".claude" / "settings.json",
target / ".claude" / "commands" / "prep.md",
@@ -127,7 +126,7 @@ def test_init_project_creates_all_expected_files(tmp_path):
created_basenames = [Path(f).name for f in result["created_files"]]
for f in expected_files:
assert f.name in created_basenames or f.exists(), f"Expected {f.name} in created_files"
assert len(result["created_files"]) >= 12
assert len(result["created_files"]) >= 11
def test_init_project_return_dict_structure(tmp_path):
@@ -325,7 +324,7 @@ def test_init_project_auto_creates_target_dir(tmp_path):
assert target.is_dir()
assert result["project_name"] == "NESTED"
assert len(result["created_files"]) >= 12
assert len(result["created_files"]) >= 11
def test_init_project_defaults_name_from_directory(tmp_path):
@@ -364,7 +363,6 @@ def test_init_project_skips_existing_optional_files(tmp_path):
(target / "CLAUDE.md").write_text("# Custom CLAUDE\n", encoding="utf-8")
(target / "AGENTS.md").write_text("# Custom AGENTS\n", encoding="utf-8")
(target / "README.md").write_text("# Custom README\n", encoding="utf-8")
(target / "STATUS.local.md").write_text("# Custom status\n", encoding="utf-8")
(target / ".gitignore").write_text("# Custom\n", encoding="utf-8")
claude_dir = target / ".claude"
@@ -518,7 +516,6 @@ def test_update_project_never_touches_user_owned_files(tmp_path):
# Modify user-owned files
(target / "README.md").write_text("# My custom README\n", encoding="utf-8")
(target / "STATUS.local.md").write_text("# Custom status\n", encoding="utf-8")
(target / ".gitignore").write_text("# custom\n", encoding="utf-8")
result = update_project(target)
@@ -526,12 +523,10 @@ def test_update_project_never_touches_user_owned_files(tmp_path):
skipped = result["skipped_files"]
assert any("REGISTRY" in s for s in skipped)
assert any("README.md" in s for s in skipped)
assert any("STATUS.local.md" in s for s in skipped)
assert any(".gitignore" in s for s in skipped)
# User customisations are preserved
assert (target / "README.md").read_text(encoding="utf-8") == "# My custom README\n"
assert (target / "STATUS.local.md").read_text(encoding="utf-8") == "# Custom status\n"
def test_update_project_creates_missing_managed_dirs(tmp_path):
@@ -556,15 +551,15 @@ def test_update_project_creates_missing_managed_dirs(tmp_path):
def test_update_project_skipped_files_count(tmp_path):
"""update_project skips 4 user-owned files + existing mailbox = 5 total."""
"""update_project skips 3 user-owned files."""
target = tmp_path / "proj"
target.mkdir()
init_project(target, project_name="count")
result = update_project(target)
# 4 user-owned (registry, README, STATUS, .gitignore)
assert len(result["skipped_files"]) == 4
# 3 user-owned (registry, README, .gitignore)
assert len(result["skipped_files"]) == 3
# ---------------------------------------------------------------------------
@@ -1,6 +1,6 @@
# DEVPULSE — Branch Prompt
Injected every turn. Breadcrumbs only — details in README, --help, .trinity/, STATUS.local.md.
Injected every turn. Breadcrumbs only — details in README, --help, .trinity/, DASHBOARD.local.json.
## Identity
@@ -9,7 +9,7 @@ DEVPULSE — Patrick's primary AI collaborator, orchestration hub. Design, plan,
## How You Work
- DRONE FOR EVERYTHING. Never raw git, gh, or python -m. `drone` is on PATH — run it directly. No which, no path lookup, no verification. Just `drone @git ...`, `drone @flow ...`, `drone @ai_mail ...`. If blocked, drone is the fix — not a workaround.
- Build own directly: modules, DPLANs, FPLANs, memories, STATUS — yours, edit freely.
- Build own directly: modules, DPLANs, FPLANs, memories — yours, edit freely.
- Prototype to explore shape, hand real build to sub-agent.
- Investigate other branches freely: read, debug, test, fix small bugs. CWD stays devpulse.
- Full multi-file implementations → `drone @ai_mail dispatch @branch`.
@@ -97,7 +97,7 @@ drone, seedgo, prax, cli, ai_mail, api, flow, spawn, trigger, memory, aipass, ho
- Lean on branches for expertise. Email for architecture questions. Investigate/debug/test freely.
- Use memories freely. Rollover to @memory by design. Update .trinity/ often.
- STATUS.local.md Notepad for friction notes. Address in batches.
- local.json todos[] for friction notes. Address in batches.
- Own things → build directly. Heavy refactors → delegate sub-agent.
- CWD = identity. Visit other branches, don't move in.
- Git awareness: after completing work, `drone @git status`. Suggest commit if coherent. Don't force, don't let pile up.
@@ -128,10 +128,10 @@ Find agent via `.trinity/passport.json`. Use `dangerouslyDisableSandbox: true`.
## Memory & Tracking
- `.trinity/local.json` — session history, key learnings
- `.trinity/local.json` — session history, key learnings, todos[]
- `.trinity/observations.json` — collaboration patterns
- `STATUS.local.md` — current work, issues, todos, notepad. Feeds central STATUS.md.
- `DASHBOARD.local.json` — live state glance (refreshed by prax)
Update proactively — after milestones, /memo, topic shifts, 5+ actions without saving.
This prompt = lightweight signposts. State → .trinity/ + STATUS.local.md.
This prompt = lightweight signposts. State → .trinity/ + DASHBOARD.local.json.
+2 -2
View File
@@ -10,7 +10,7 @@ DevPulse handles the day-to-day: working with the user to plan, design, troubles
| You want to | Read |
|---|---|
| What's happening right now | [STATUS.local.md](STATUS.local.md) |
| What's happening right now | `DASHBOARD.local.json` |
| Identity, memory, session history | [`.trinity/`](.trinity/) |
| Active plans | `drone @flow list open` |
| Branch list | `drone systems` |
@@ -46,7 +46,7 @@ src/aipass/devpulse/
├── artifacts/ # Birth certificate, reports
├── dropbox/ # Received files, archived plans, install audit
├── docs/ # Transition notes
└── STATUS.local.md # Current work beacon
└── DASHBOARD.local.json # Live state (refreshed by prax)
```
## Commands
@@ -89,7 +89,6 @@ Steps:
## 8. Wrap
- [ ] Update `.trinity/` memories (session log: what merged, PR#, tag)
- [ ] Update `STATUS.local.md`
- [ ] Fill **Run Summary** below (PR numbers, tag, anything that broke)
- [ ] Close this playbook → vectorizes the run
@@ -1,6 +1,6 @@
# HOOKS -- Branch Prompt
Injected every turn. Breadcrumbs only -- details in README, --help, .trinity/, STATUS.local.md.
Injected every turn. Breadcrumbs only -- details in README, --help, .trinity/.
## Identity
+1 -3
View File
@@ -10,7 +10,6 @@ Every hook event flows through one engine. Platform bridges normalize the event
| You want to | Read |
|---|---|
| What's happening right now | [STATUS.local.md](STATUS.local.md) |
| Identity, memory, session history | [`.trinity/`](.trinity/) |
| Hook engine design | `DPLAN-0184` |
| Per-project config | `.aipass/hooks.json` |
@@ -78,8 +77,7 @@ src/aipass/hooks/
│ └── diagnostics.py # JSONL logging for hook execution
├── logs/
│ └── engine.jsonl # JSONL diagnostics (every hook execution)
├── tests/ # 385 tests across 20 test files
└── STATUS.local.md
└── tests/ # 385 tests across 20 test files
```
## How It Works
@@ -31,17 +31,6 @@ def _find_branch_dir(cwd: str) -> Path | None:
return None
def _read_status_local(branch_dir: Path) -> str | None:
for name in ("STATUS.local.md", "dev.local.md"):
path = branch_dir / name
if path.is_file():
try:
return path.read_text(encoding="utf-8")[:3000]
except Exception as exc:
logger.info("[HOOKS] compact: read status failed: %s", exc)
return None
def _read_last_session(branch_dir: Path) -> str | None:
local_path = branch_dir / ".trinity" / "local.json"
if not local_path.is_file():
@@ -115,18 +104,13 @@ def handle(hook_data: dict) -> dict:
if session_info:
sections.append(f"## Last Session\n{session_info}")
status = _read_status_local(branch_dir)
if status:
sections.append(f"## STATUS.local.md\n{status}")
is_dispatched = os.environ.get("AIPASS_SESSION_TYPE") == "dispatched"
if is_dispatched:
sections.append(
"## DISPATCHED AGENT — SAVE STATE NOW\n"
"Before continuing work, you MUST update your memories:\n"
"1. Update .trinity/local.json — add/update current session with work done so far\n"
"2. Update STATUS.local.md — ensure Current Work reflects what you've accomplished\n"
"3. Then continue your task from where the summary left off\n\n"
"2. Then continue your task from where the summary left off\n\n"
"This is non-optional. Compaction just happened — if you don't save now, work history is lost."
)
else:
@@ -134,7 +118,6 @@ def handle(hook_data: dict) -> dict:
"## Recovery Protocol\n"
"- Continue where the summary left off — don't restart or ask generic questions\n"
"- .trinity/local.json has full session history and key_learnings — read it if you need more context\n"
"- STATUS.local.md has current work, known issues, and todos\n"
"- Save memories proactively — compaction just proved you need to\n"
"- Match the conversation tone from before compaction"
)
+3 -4
View File
@@ -29,9 +29,6 @@ class TestCompactHandler:
),
encoding="utf-8",
)
status = tmp_path / "STATUS.local.md"
status.write_text("# Status\nCurrent work here", encoding="utf-8")
with patch("aipass.hooks.apps.handlers.lifecycle.compact.speak"):
with patch("aipass.hooks.apps.handlers.lifecycle.compact._get_git_info", return_value="Git branch: dev"):
result = handle({"cwd": str(tmp_path)})
@@ -40,7 +37,7 @@ class TestCompactHandler:
assert "POST-COMPACT RECOVERY" in result["stdout"]
assert "Git branch: dev" in result["stdout"]
assert "did stuff" in result["stdout"]
assert "Current work here" in result["stdout"]
assert "STATUS.local.md" not in result["stdout"]
def test_returns_recovery_when_no_branch_dir(self):
from aipass.hooks.apps.handlers.lifecycle.compact import handle
@@ -64,6 +61,7 @@ class TestCompactHandler:
result = handle({"cwd": str(tmp_path)})
assert "SAVE STATE NOW" in result["stdout"]
assert "STATUS.local.md" not in result["stdout"]
def test_interactive_gets_recovery_protocol(self, tmp_path):
from aipass.hooks.apps.handlers.lifecycle.compact import handle
@@ -77,6 +75,7 @@ class TestCompactHandler:
result = handle({"cwd": str(tmp_path)})
assert "Recovery Protocol" in result["stdout"]
assert "STATUS.local.md" not in result["stdout"]
def test_empty_hook_data(self):
from aipass.hooks.apps.handlers.lifecycle.compact import handle
@@ -221,6 +221,11 @@ def _apply_template_to_local(current: dict, template: dict, branch_name: str) ->
data["key_learnings"] = {}
changes.append("key_learnings: added (empty)")
# Todos: add if missing (operational list, not rolled over)
if "todos" not in data:
data["todos"] = []
changes.append("todos: added (empty)")
# Active tasks: ensure recently_completed exists
if "active_tasks" in data and isinstance(data["active_tasks"], dict):
if "recently_completed" not in data["active_tasks"]:
@@ -1,16 +1,17 @@
{
"last_push": "2026-04-22 20:07:31",
"last_push": "2026-06-07 21:40:48",
"last_push_branches": [
"AI_MAIL",
"API",
"CLI",
"DEVPULSE",
"DRONE",
"FLOW",
"MEMORY",
"PRAX",
"SEEDGO",
"SPAWN",
"TRIGGER"
"HOOKS",
"aipass",
"api",
"cli",
"devpulse",
"drone",
"flow",
"memory",
"prax",
"seedgo",
"spawn",
"trigger"
]
}
@@ -15,9 +15,11 @@
"limits": {
"max_sessions": 20,
"max_key_learnings": 25,
"max_todos": 10,
"session_summary_max_chars": 150,
"learning_value_max_chars": 200,
"note": "DO NOT trim, prune, or delete entries. Rollover to @memory handles overflow automatically. Just add new entries."
"todo_text_max_chars": 200,
"note": "DO NOT trim, prune, or delete entries. Rollover to @memory handles overflow automatically. Just add new entries. todos[] is OPERATIONAL — completing/removing items is normal, not data destruction."
},
"status": {
"health": "healthy",
@@ -26,6 +28,7 @@
}
},
"key_learnings": {},
"todos": [],
"sessions": [
{
"session_number": 1,
+113
View File
@@ -12,6 +12,7 @@ Covers:
- rollover/extractor.py (_extract_items_v2, _detect_growing_array, helpers)
- tracking/line_counter.py (_count_physical_lines, update_line_count)
- schema/normalize.py (normalize_memory_file)
- todos[] operational schema (rollover ignores, caps enforced)
All tests use mocks/tmp_path -- no live filesystem or infrastructure access.
"""
@@ -542,3 +543,115 @@ class TestNormalizeMemoryFile:
assert "max_word_count" not in data["document_metadata"]["limits"]
assert "max_token_count" not in data["document_metadata"]["limits"]
assert data["document_metadata"]["limits"]["max_lines"] == 600
class TestTodosOperational:
"""Confirm rollover treats todos[] as operational — never vectorized or trimmed."""
def _make_data_with_todos(self, num_sessions=5, max_sessions=3, num_todos=5):
"""Build v2 data that includes a populated todos[] array."""
sessions = [
{"session_number": i, "date": f"2026-01-{i:02d}", "summary": f"Session {i}"}
for i in range(1, num_sessions + 1)
]
todos = [{"id": f"t{i}", "text": f"Todo item {i}", "created": f"2026-06-0{i}"} for i in range(1, num_todos + 1)]
return {
"document_metadata": {
"schema_version": "2.0.0",
"limits": {
"max_sessions": max_sessions,
"max_key_learnings": 25,
"max_todos": 10,
"todo_text_max_chars": 200,
},
"status": {"current_lines": 100},
},
"sessions": sessions,
"key_learnings": {},
"todos": todos,
}
def test_v2_extraction_leaves_todos_untouched(self, monkeypatch, tmp_path):
"""v2 rollover trims sessions but never touches todos[]."""
ext, _ = _import_extractor(monkeypatch)
data = self._make_data_with_todos(num_sessions=6, max_sessions=3, num_todos=5)
mem_file = tmp_path / ".trinity" / "local.json"
mem_file.parent.mkdir(parents=True)
mem_file.write_text(json.dumps(data, indent=2), encoding="utf-8")
def fake_write(fp, d):
fp.write_text(json.dumps(d, indent=2), encoding="utf-8")
with patch.object(ext, "_write_memory_file", side_effect=fake_write):
result = ext._extract_items_v2(mem_file, data)
assert result["success"] is True
assert len(data["sessions"]) == 3
assert len(data["todos"]) == 5
assert data["todos"][0]["id"] == "t1"
assert data["todos"][4]["id"] == "t5"
def test_v1_detect_growing_array_ignores_todos(self, monkeypatch):
"""v1 _detect_growing_array does NOT consider todos as a growing array."""
ext, _ = _import_extractor(monkeypatch)
data = {
"sessions": [{"session_number": 1}],
"todos": [{"id": "t1", "text": "Something"}],
}
result = ext._detect_growing_array(data)
assert result == "sessions"
def test_v1_detect_growing_array_skips_todos_only(self, monkeypatch):
"""If only todos[] exists (no memory arrays), _detect_growing_array returns None."""
ext, _ = _import_extractor(monkeypatch)
data = {
"todos": [{"id": "t1", "text": "Something"}],
"key_learnings": {"k1": "v1"},
}
result = ext._detect_growing_array(data)
assert result is None
def test_todos_schema_shape(self):
"""Validate the expected todos[] item schema: id, text, created, optional priority."""
todo_item = {"id": "t1", "text": "Fix the bug", "created": "2026-06-07"}
assert "id" in todo_item
assert "text" in todo_item
assert "created" in todo_item
todo_with_priority = {**todo_item, "priority": "high"}
assert todo_with_priority["priority"] == "high"
def test_todos_cap_enforced(self):
"""max_todos and todo_text_max_chars are the cap boundaries."""
limits = {
"max_todos": 10,
"todo_text_max_chars": 200,
}
todos = [{"id": f"t{i}", "text": "x" * 200, "created": "2026-06-07"} for i in range(10)]
assert len(todos) <= limits["max_todos"]
assert all(len(t["text"]) <= limits["todo_text_max_chars"] for t in todos)
over_cap = todos + [{"id": "t11", "text": "extra", "created": "2026-06-07"}]
assert len(over_cap) > limits["max_todos"]
def test_todos_survives_full_extraction_cycle(self, monkeypatch, tmp_path):
"""End-to-end: extract_items on a file with todos[] preserves them completely."""
ext, _ = _import_extractor(monkeypatch)
data = self._make_data_with_todos(num_sessions=25, max_sessions=20, num_todos=8)
mem_file = tmp_path / ".trinity" / "local.json"
mem_file.parent.mkdir(parents=True)
mem_file.write_text(json.dumps(data, indent=2), encoding="utf-8")
def fake_write(fp, d):
fp.write_text(json.dumps(d, indent=2), encoding="utf-8")
with patch.object(ext, "_write_memory_file", side_effect=fake_write):
result = ext._extract_items_v2(mem_file, data)
assert result["success"] is True
assert result["extracted_count"] > 0
assert len(data["todos"]) == 8
for i, todo in enumerate(data["todos"], 1):
assert todo["id"] == f"t{i}"
+9 -2
View File
@@ -62,6 +62,13 @@
"subject": "Codex + Gemini log monitoring adapters for prax monitor",
"date_closed": "2026-04-10",
"location": "prax"
},
{
"plan_id": "FPLAN-0245",
"type": "FPLAN",
"subject": "STATUS decommission — prax dashboard todo_count + engine dormant",
"date_closed": "2026-06-08",
"location": "prax"
}
],
"document_metadata": {
@@ -71,14 +78,14 @@
"tags": [
"session_tracking",
"work_log",
"PRAX"
"prax"
],
"limits": {
"max_sessions": 20,
"max_key_learnings": 25,
"session_summary_max_chars": 150,
"learning_value_max_chars": 200,
"note": "Auto-rollover to @memory when limits exceeded. Oldest entries trimmed first."
"note": "DO NOT trim, prune, or delete entries. Rollover to @memory handles overflow automatically. Just add new entries."
},
"status": {
"health": "healthy",
+6 -6
View File
@@ -13,7 +13,7 @@
Prax is the logging and monitoring backbone of the AIPass ecosystem. Any branch imports `logger` and gets automatic log routing — prax detects the caller via stack introspection and writes to the correct per-module log file. No configuration needed.
On top of logging, prax provides Mission Control (a real-time terminal console for file changes, log events, and agent activity), a log audit system, a dashboard infrastructure, and cross-branch STATUS.md synchronization.
On top of logging, prax provides Mission Control (a real-time terminal console for file changes, log events, and agent activity), a log audit system, and a dashboard infrastructure.
## Quick Start
@@ -60,7 +60,7 @@ Interactive commands inside the monitor: `help`, `status`, `quit`/`exit`.
```bash
drone @prax status # System health (modules, loggers, watcher state)
drone @prax status sync # Build STATUS.md from all branch STATUS.local.md
drone @prax status sync # DORMANT — STATUS.md sync decommissioned (TDPLAN-0007)
drone @prax status --help # Status usage
```
@@ -127,7 +127,7 @@ prax/
│ │ ├── logger.py # SystemLogger — auto-routing, two-tier logging
│ │ ├── monitor.py # Mission Control — 3-thread real-time monitoring
│ │ ├── dashboard.py # Dashboard — template management, refresh, write-through
│ │ ├── status.py # System status — health display, STATUS.md sync
│ │ ├── status.py # System status — health display (STATUS.md sync dormant)
│ │ └── log_audit.py # Log audit — scan, health summary, enforce limits
│ └── handlers/ # Implementation details (11 handler directories)
│ ├── central/ # Central file reader (.ai_central/*.central.json)
@@ -139,7 +139,7 @@ prax/
│ ├── logging/ # Setup, rotation, introspection, override, direct logger
│ ├── monitoring/ # Event queue, branch detector, stream output, log watcher
│ ├── registry/ # Module registry load/save
│ ├── status/ # STATUS.md sync handler
│ ├── status/ # STATUS.md sync handler (dormant — TDPLAN-0007)
│ └── watcher/ # Background system watchers
├── prax_json/ # Auto-created per-module config/data/log files
├── templates/ # Dashboard template schema (DASHBOARD.template.json)
@@ -167,7 +167,7 @@ drone @prax monitor run
4. **Mission Control** — Three threads: display worker (pulls from event queue), file watcher (watchdog on branch `apps/` dirs), log watcher (tails `system_logs/*.log`). Falls back to polling when inotify is exhausted.
5. **Multi-CLI monitoring** — Watches Claude Code JSONL and Codex JSONL session files. Extracts agent activity (thinking, tool use, responses) with model detection and branch resolution.
6. **Dashboard** — Template-based per-branch dashboard files. Refreshes from central files (`*.central.json`). Write-through API for services to update sections directly.
7. **STATUS sync** — Scans all branch `STATUS.local.md` files, extracts State/Last update fields, builds aggregated `STATUS.md` at the repo root.
7. **STATUS sync** — *(Dormant — TDPLAN-0007)* Previously scanned all branch `STATUS.local.md` files and built aggregated `STATUS.md`. Engine code intact but no longer triggered.
## Tests
@@ -208,7 +208,7 @@ drone @prax monitor run
- All branches — Unified logging via `from aipass.prax import logger`
- All branches — Real-time monitoring via Mission Control
- All branches — Per-branch dashboard files
- System — `STATUS.md` sync, log audit enforcement
- System — Log audit enforcement
## Known Issues
- **inotify exhaustion** — System often near `max_user_watches` limit. Monitor uses polling fallback (functional but slower).
@@ -203,10 +203,12 @@ def _calculate_quick_status_standalone(sections: Dict) -> Dict:
"""
ai_mail = sections.get("ai_mail", {})
flow = sections.get("flow", {})
todo = sections.get("todo", {})
new_mail_raw = ai_mail.get("new", ai_mail.get("unread", 0))
opened_raw = ai_mail.get("opened", 0)
active_plans_raw = flow.get("active_plans", 0)
todo_count = int(todo.get("todo_count", 0) or 0)
new_mail = len(new_mail_raw) if isinstance(new_mail_raw, list) else int(new_mail_raw or 0)
opened_mail = len(opened_raw) if isinstance(opened_raw, list) else int(opened_raw or 0)
@@ -221,11 +223,14 @@ def _calculate_quick_status_standalone(sections: Dict) -> Dict:
parts.append(f"{opened_mail} opened")
if active_plans > 0:
parts.append(f"{active_plans} active plans")
if todo_count > 0:
parts.append(f"{todo_count} todos")
return {
"new_mail": new_mail,
"opened_mail": opened_mail,
"active_plans": active_plans,
"todo_count": todo_count,
"action_required": action_required,
"summary": ", ".join(parts) if parts else "All clear",
}
@@ -46,10 +46,12 @@ def calculate_quick_status(sections: Dict) -> Dict:
"""
ai_mail = sections.get("ai_mail", {})
flow = sections.get("flow", {})
todo = sections.get("todo", {})
new_mail = ai_mail.get("new", ai_mail.get("unread", 0))
opened_mail = ai_mail.get("opened", 0)
active_plans = flow.get("active_plans", 0)
todo_count = todo.get("todo_count", 0)
action_required = new_mail > 0 or active_plans > 0
@@ -60,11 +62,14 @@ def calculate_quick_status(sections: Dict) -> Dict:
summary_parts.append(f"{opened_mail} opened")
if active_plans:
summary_parts.append(f"{active_plans} active plans")
if todo_count:
summary_parts.append(f"{todo_count} todos")
result = {
"new_mail": new_mail,
"opened_mail": opened_mail,
"active_plans": active_plans,
"todo_count": todo_count,
"action_required": action_required,
"summary": ", ".join(summary_parts) if summary_parts else "All clear",
}
@@ -75,6 +80,7 @@ def calculate_quick_status(sections: Dict) -> Dict:
"action_required": action_required,
"new_mail": new_mail,
"active_plans": active_plans,
"todo_count": todo_count,
},
)
@@ -76,6 +76,7 @@ DASHBOARD_TEMPLATE = {
"new_mail": 0,
"opened_mail": 0,
"active_plans": 0,
"todo_count": 0,
"action_required": False,
"summary": "",
},
+1 -1
View File
@@ -380,7 +380,7 @@ def _get_watch_directories(repo_root: Path) -> list[tuple[Path, bool]]:
apps_dir = branch_path / "apps"
if apps_dir.exists():
dirs.append((apps_dir, True))
# Branch root non-recursive — STATUS.local.md, README.md
# Branch root non-recursive — README.md
dirs.append((branch_path, False))
# .trinity/ non-recursive — identity files
trinity_dir = branch_path / ".trinity"
@@ -41,6 +41,7 @@ def refresh(branch_path: Optional[Path] = None) -> Dict:
("git", _refresh_git),
("session", _refresh_session),
("dispatch", _refresh_dispatch),
("todo", _refresh_todo),
]
for name, builder_fn in builders:
@@ -70,3 +71,9 @@ def _refresh_dispatch(branch_path: Path) -> None:
from .dispatch_section import build_dispatch_section
build_dispatch_section(branch_path)
def _refresh_todo(branch_path: Path) -> None:
from .todo_section import build_todo_section
build_todo_section(branch_path)
@@ -0,0 +1,61 @@
# =================== AIPass ====================
# Name: todo_section.py
# Description: Todo section builder for devpulse dashboard
# Version: 1.0.0
# Created: 2026-06-07
# Modified: 2026-06-07
# =============================================
"""Todo section builder for devpulse dashboard plugin.
Reads todos[] from .trinity/local.json and writes a 'todo' section
(managed_by: devpulse, todo_count, todos) via write_section().
"""
import json
from pathlib import Path
from typing import Dict
from aipass.prax.apps.modules.dashboard import write_section
from aipass.prax.apps.modules.logger import system_logger as logger
def build_todo_section(branch_path: Path) -> bool:
"""Build todo section data and write to dashboard.
Args:
branch_path: Path to devpulse branch root.
Returns:
True if write_section succeeded, False otherwise.
"""
local_json_path = branch_path / ".trinity" / "local.json"
if not local_json_path.exists():
section_data: Dict = {
"managed_by": "devpulse",
"todo_count": 0,
"todos": [],
}
return write_section(branch_path, "todo", section_data)
try:
data = json.loads(local_json_path.read_text())
except (json.JSONDecodeError, OSError) as exc:
logger.warning("Failed to read local.json at %s: %s", local_json_path, exc)
section_data = {
"managed_by": "devpulse",
"todo_count": 0,
"todos": [],
}
return write_section(branch_path, "todo", section_data)
todos = data.get("todos", [])
section_data = {
"managed_by": "devpulse",
"todo_count": len(todos),
"todos": todos,
}
return write_section(branch_path, "todo", section_data)
@@ -219,13 +219,101 @@ class TestDispatchSection:
assert dispatch["details"]["seedgo"]["subject"] == "unknown"
class TestTodoSection:
"""Tests for todo_section.py."""
def test_build_todo_section_with_todos(self, branch_path):
"""Test todo section reads todos[] from local.json correctly."""
from aipass.prax.apps.plugins.devpulse_dashboard.todo_section import build_todo_section
trinity = branch_path / ".trinity"
trinity.mkdir()
local_data = {
"todos": [
{"id": "t1", "text": "Fix the bug", "created": "2026-06-07"},
{"id": "t2", "text": "Write tests", "created": "2026-06-07", "priority": "high"},
],
}
(trinity / "local.json").write_text(json.dumps(local_data))
result = build_todo_section(branch_path)
assert result is True
dash = json.loads((branch_path / "DASHBOARD.local.json").read_text())
todo = dash["sections"]["todo"]
assert todo["managed_by"] == "devpulse"
assert todo["todo_count"] == 2
assert len(todo["todos"]) == 2
assert todo["todos"][0]["id"] == "t1"
def test_build_todo_section_empty_todos(self, branch_path):
"""Test todo section with empty todos list."""
from aipass.prax.apps.plugins.devpulse_dashboard.todo_section import build_todo_section
trinity = branch_path / ".trinity"
trinity.mkdir()
(trinity / "local.json").write_text(json.dumps({"todos": []}))
result = build_todo_section(branch_path)
assert result is True
dash = json.loads((branch_path / "DASHBOARD.local.json").read_text())
todo = dash["sections"]["todo"]
assert todo["todo_count"] == 0
assert todo["todos"] == []
def test_build_todo_section_no_local_json(self, branch_path):
"""Test todo section when local.json doesn't exist."""
from aipass.prax.apps.plugins.devpulse_dashboard.todo_section import build_todo_section
result = build_todo_section(branch_path)
assert result is True
dash = json.loads((branch_path / "DASHBOARD.local.json").read_text())
todo = dash["sections"]["todo"]
assert todo["todo_count"] == 0
assert todo["todos"] == []
def test_build_todo_section_corrupt_json(self, branch_path):
"""Test todo section handles corrupt local.json."""
from aipass.prax.apps.plugins.devpulse_dashboard.todo_section import build_todo_section
trinity = branch_path / ".trinity"
trinity.mkdir()
(trinity / "local.json").write_text("not valid json{{{")
result = build_todo_section(branch_path)
assert result is True
dash = json.loads((branch_path / "DASHBOARD.local.json").read_text())
todo = dash["sections"]["todo"]
assert todo["todo_count"] == 0
def test_build_todo_section_no_todos_key(self, branch_path):
"""Test todo section when local.json has no todos key."""
from aipass.prax.apps.plugins.devpulse_dashboard.todo_section import build_todo_section
trinity = branch_path / ".trinity"
trinity.mkdir()
(trinity / "local.json").write_text(json.dumps({"sessions": []}))
result = build_todo_section(branch_path)
assert result is True
dash = json.loads((branch_path / "DASHBOARD.local.json").read_text())
todo = dash["sections"]["todo"]
assert todo["todo_count"] == 0
assert todo["todos"] == []
class TestRefresh:
"""Tests for refresh.py orchestrator."""
@patch("aipass.prax.apps.plugins.devpulse_dashboard.todo_section.build_todo_section")
@patch("aipass.prax.apps.plugins.devpulse_dashboard.git_section.build_git_section")
@patch("aipass.prax.apps.plugins.devpulse_dashboard.session_section.build_session_section")
@patch("aipass.prax.apps.plugins.devpulse_dashboard.dispatch_section.build_dispatch_section")
def test_refresh_all_success(self, mock_dispatch, mock_session, mock_git, branch_path):
def test_refresh_all_success(self, mock_dispatch, mock_session, mock_git, mock_todo, branch_path):
"""Test refresh orchestrator calls all builders."""
from aipass.prax.apps.plugins.devpulse_dashboard.refresh import refresh
@@ -233,14 +321,17 @@ class TestRefresh:
assert results["git"]["success"] is True
assert results["session"]["success"] is True
assert results["dispatch"]["success"] is True
assert results["todo"]["success"] is True
mock_git.assert_called_once_with(branch_path)
mock_session.assert_called_once_with(branch_path)
mock_dispatch.assert_called_once_with(branch_path)
mock_todo.assert_called_once_with(branch_path)
@patch("aipass.prax.apps.plugins.devpulse_dashboard.todo_section.build_todo_section")
@patch("aipass.prax.apps.plugins.devpulse_dashboard.git_section.build_git_section")
@patch("aipass.prax.apps.plugins.devpulse_dashboard.session_section.build_session_section")
@patch("aipass.prax.apps.plugins.devpulse_dashboard.dispatch_section.build_dispatch_section")
def test_refresh_partial_failure(self, mock_dispatch, mock_session, mock_git, branch_path):
def test_refresh_partial_failure(self, mock_dispatch, mock_session, mock_git, mock_todo, branch_path):
"""Test refresh continues when one section fails."""
from aipass.prax.apps.plugins.devpulse_dashboard.refresh import refresh
@@ -250,6 +341,7 @@ class TestRefresh:
assert "No .git" in results["git"]["error"]
assert results["session"]["success"] is True
assert results["dispatch"]["success"] is True
assert results["todo"]["success"] is True
def test_refresh_default_path(self):
"""Test refresh uses DEVPULSE_PATH by default."""
+1 -2
View File
@@ -123,8 +123,7 @@ seedgo/
├── .trinity/ # Identity + memory
├── .seedgo/ # Self-bypass rules
├── .ai_mail.local/ # Mailbox
├── CLAUDE.md # Branch startup instructions
└── STATUS.local.md # Current state summary
└── CLAUDE.md # Branch startup instructions
```
### Key Patterns
@@ -47,7 +47,6 @@ _RUNTIME_ARTIFACTS = {
"system_logs",
"docs.local",
".trinity",
"STATUS.local.md",
"DASHBOARD.local.json",
}
@@ -486,7 +486,7 @@ def test_is_runtime_artifact_known_dirs():
assert _is_runtime_artifact(Path("/any/path/.trinity")) is True
assert _is_runtime_artifact(Path("/any/path/cli_json")) is True
assert _is_runtime_artifact(Path("/any/path/seedgo_json")) is True
assert _is_runtime_artifact(Path("/any/path/STATUS.local.md")) is True
assert _is_runtime_artifact(Path("/any/path/STATUS.local.md")) is False
assert _is_runtime_artifact(Path("/any/path/DASHBOARD.local.json")) is True
assert _is_runtime_artifact(Path("/any/path/docs.local")) is True
assert _is_runtime_artifact(Path("/any/path/dropbox")) is True
@@ -1,6 +1,6 @@
# SPAWN — Branch Prompt
*Injected every turn. Breadcrumbs only — details: README, --help, .trinity/ memories, STATUS.local.md.*
*Injected every turn. Breadcrumbs only — details: README, --help, .trinity/ memories.*
## Identity
@@ -17,7 +17,7 @@ PRINCIPLES:
- Include concrete reference data — lookup tables, checklists, decision trees.
- If you can run `drone @{{BRANCH}} --help` to get it, don't duplicate it here.
- This file should be STABLE. If it changes every session, that content belongs in
STATUS.local.md or .trinity/ instead.
.trinity/ instead.
WHEN YOU'RE DONE:
- Delete this instruction block
@@ -25,7 +25,7 @@ WHEN YOU'RE DONE:
- What remains should be pure operational reference
-->
*Injected every turn. Breadcrumbs only — details in README, --help, .trinity/ memories, STATUS.local.md.*
*Injected every turn. Breadcrumbs only — details in README, --help, .trinity/ memories.*
## Identity
@@ -17,7 +17,7 @@ PRINCIPLES:
- Include concrete reference data — lookup tables, checklists, decision trees.
- If you can run `drone @{{BRANCH}} --help` to get it, don't duplicate it here.
- This file should be STABLE. If it changes every session, that content belongs in
STATUS.local.md or .trinity/ instead.
.trinity/ instead.
WHEN YOU'RE DONE:
- Delete this instruction block
@@ -25,7 +25,7 @@ WHEN YOU'RE DONE:
- What remains should be pure operational reference
-->
*Injected every turn. Breadcrumbs only — details in README, --help, .trinity/ memories, STATUS.local.md.*
*Injected every turn. Breadcrumbs only — details in README, --help, .trinity/ memories.*
## Identity
@@ -26,7 +26,7 @@
"f004": {
"path": ".aipass/aipass_local_prompt.md",
"name": "aipass_local_prompt.md",
"content_hash": "5f8e2a3859a9",
"content_hash": "bf82b35fa7d5",
"has_branch_placeholder": false
},
"f005": {
@@ -113,12 +113,6 @@
"content_hash": "ad99517a50f9",
"has_branch_placeholder": false
},
"f018": {
"path": "STATUS.local.md",
"name": "STATUS.local.md",
"content_hash": "059295ad4d4e",
"has_branch_placeholder": false
},
"f019": {
"path": "apps/README.md",
"name": "README.md",
@@ -155,7 +149,7 @@
"content_hash": "a4cf0a8e3b4f",
"has_branch_placeholder": false
},
"f015": {
"f018": {
"path": "apps/modules/__init__.py",
"name": "__init__.py",
"content_hash": "e3b0c44298fc",
@@ -269,7 +263,7 @@
"content_hash": "28e9ae373563",
"has_branch_placeholder": false
},
"f026": {
"f015": {
"path": "apps/plugins/__init__.py",
"name": "__init__.py",
"content_hash": "e3b0c44298fc",
@@ -1,15 +0,0 @@
# @{{BRANCHNAME}}
> {{BRANCHNAME}} branch
**State:** Not Started
**Last update:** None
## Milestones
-
## Current Work
-
## Known Issues
-
+6
View File
@@ -375,6 +375,12 @@
"pattern": "handle_pr",
"reason": "PR event handlers (handle_pr_created, handle_pr_merged) are event handlers registered in registry.py, not trigger-specific naming violations."
},
{
"file": "apps/handlers/events/pr_status_sync.py",
"standard": "unused_function",
"pattern": "handle_pr_created, handle_pr_merged",
"reason": "TDPLAN-0007: status-sync decommissioned. Handlers unregistered in registry.py but module + tests kept intact for future revival."
},
{
"file": "apps/handlers/log_watcher.py",
"standard": "silent_catch",
+5 -5
View File
@@ -74,7 +74,7 @@ result = report_error(
## Events
15 events registered via `handlers/events/registry.py` on first `Trigger.fire()`. All fire through the event bus.
15 events defined, 13 active (2 decommissioned by TDPLAN-0007). Registered via `handlers/events/registry.py` on first `Trigger.fire()`. All fire through the event bus.
| Event | Handler | Trigger | Action |
|-------|---------|---------|--------|
@@ -90,8 +90,8 @@ result = report_error(
| `memory_template_updated` | `memory_template_updated.py` | Memory template changed | Pushes template updates to branches |
| `memory_saved` | `memory.py` | Memory file written | Placeholder for future rollover trigger |
| `cli_header_displayed` | `cli.py` | CLI displays headers | Registration hook |
| `pr_created` | `pr_status_sync.py` | PR opened on GitHub | Runs `drone @prax status sync` (fire-and-forget) |
| `pr_merged` | `pr_status_sync.py` | PR merged on GitHub | Runs `drone @prax status sync` (fire-and-forget) |
| `pr_created` | `pr_status_sync.py` | PR opened on GitHub | ~~Runs `drone @prax status sync`~~ **Decommissioned** (TDPLAN-0007) |
| `pr_merged` | `pr_status_sync.py` | PR merged on GitHub | ~~Runs `drone @prax status sync`~~ **Decommissioned** (TDPLAN-0007) |
| `memory_pool_auto_processed` | `memory_pool.py` | Hook engine runs `auto_process()` | Logs result; on failure fires `error_detected` for Medic dispatch |
## Medic
@@ -148,7 +148,7 @@ trigger/
│ ├── json/
│ │ └── json_handler.py # JSON structure logging
│ ├── events/
│ │ ├── registry.py # Auto-registers all 15 event handlers
│ │ ├── registry.py # Auto-registers 13 active event handlers
│ │ ├── startup.py # Startup catch-up scan
│ │ ├── error_detected.py # 8-gate Medic dispatch
│ │ ├── error_logged.py # Monitor-only (no dispatch)
@@ -159,7 +159,7 @@ trigger/
│ │ ├── memory_template_updated.py
│ │ ├── memory.py # memory_saved placeholder
│ │ ├── cli.py # cli_header_displayed hook
│ │ ├── pr_status_sync.py # PR → prax status sync
│ │ ├── pr_status_sync.py # PR → prax status sync (decommissioned TDPLAN-0007)
│ │ └── memory_pool.py # Pool auto-process observability
│ └── watchers/
│ └── log_watcher.py # System log watcher (system_logs/ dir)
@@ -63,7 +63,8 @@ def setup_handlers():
from .warning_logged import handle_warning_logged
from .bulletin_created import handle_bulletin_created
from .memory_template_updated import handle_memory_template_updated
from .pr_status_sync import handle_pr_created, handle_pr_merged
# from .pr_status_sync import handle_pr_created, handle_pr_merged # TDPLAN-0007: status-sync decommissioned
from .memory_pool import handle_memory_pool_auto_processed
trigger.on("startup", handle_startup)
@@ -75,8 +76,8 @@ def setup_handlers():
trigger.on("warning_logged", handle_warning_logged)
trigger.on("bulletin_created", handle_bulletin_created)
trigger.on("memory_template_updated", handle_memory_template_updated)
trigger.on("pr_created", handle_pr_created)
trigger.on("pr_merged", handle_pr_merged)
# trigger.on("pr_created", handle_pr_created) # TDPLAN-0007: status-sync decommissioned
# trigger.on("pr_merged", handle_pr_merged) # TDPLAN-0007: status-sync decommissioned
trigger.on("memory_pool_auto_processed", handle_memory_pool_auto_processed)
json_handler.log_operation("handlers_registered", {"success": True})