seedgo's cli/help_text/introspection standards are static source scans — they confirm a print_help function, console.print, and --help wiring exist, but never execute --help. So a module could score 100% while rendering raw argparse. ai_mail did exactly that via console.print(parser.format_help()), laundering argparse plain text through the approved console API and dodging the existing parser.print_help() ban. - seedgo: cli_check now flags .format_help(); cli.md/cli_content.py name it alongside print_help(); +2 regression tests (1095 pass, self-audit 100%) - ai_mail: rewrote print_help() to hand-rolled Rich (737 tests pass); --help now renders Rich with no raw argparse, Cli back to 100% legitimately - behavioral --help check (run it, assert not raw argparse) noted as a follow-up DPLAN-0217. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QEQZXCtgnF3NQtcttTErpq
73 KiB
Changelog
All notable changes to AIPass will be documented in this file.
The format is based on Keep a Changelog.
Entries are grouped by merge under a dated section header (YYYY-MM-DD). Package
releases follow SemVer and are tracked by the git tag and
PyPI version — not the changelog header.
[2026-06-24]
Fixed
- seedgo CLI help checkers green-lit non-compliant
--helpoutput — thecli/help_text/introspectionstandards are static source scans (they confirm aprint_helpfunction,console.print, and--helpwiring exist) but never execute--help, so a module could score 100% while rendering raw argparse.@ai_maildid exactly that viaconsole.print(parser.format_help()), laundering argparse's plain text through the approved console API and dodging the existingparser.print_help()ban. Closed the loophole:cli_checknow flags.format_help(),cli.md/cli_content.pyname it alongsideprint_help(), +2 regression tests. Also rewrote@ai_mail'sprint_help()to render hand-rolled Rich (the--helpcontent was complete, just unstyled). A behavioral--helpcheck (run it, assert not raw argparse) is noted as a follow-up. (DPLAN-0217) - seedgo
readme_checkignored the(disabled)marker in self-scans — its module-list and test-count scans now skipfoo(disabled).py, matching the central audit collector. An in-place disabled module no longer trips a false "missing module" violation; disabled test files no longer inflate README test counts (td-103). - seedgo
unused_functionbypasses are now name-scoped — bypasses match by function name (functions: [...]) instead of line number (lines: [...]), which drifted silently when code shifted and re-flagged exempted functions (bit us S216/S217).linesstays supported for other standards. Migrated the 10 existing line-scoped entries across drone/memory/skills and dropped 3 dead entries already pointing past EOF (td-009). - Dispatch footer no longer tells workers to close the orchestrator's plan —
the standard email footer's checklist item read
CLOSE FPLAN → drone @flow close <plan_id>, which led dispatched agents to close the master/parent plan referenced in their brief (bit us in FPLAN-0260). Reworded toCLOSE YOUR PLAN → ... this task's plan only, never the master/parent— a worker still closes the sub-plan handed to it, but the master stays the orchestrator's to close on completion (td-6).
Changed
- Backup
.backupignoredefault moved out of code into a template file — the seed content backup writes into a new project's.backupignorenow lives inbackup/templates/backupignore.template(loaded at register), matching the AIPass convention that templates are data files, not hardcoded Python. Retired theBUILTIN_IGNORESlist;_build_backupignore()reads the template and raises if it's missing — never silently empty, since an empty.backupignorewould back up everything and crash. Docs/comments repointed to the template (td-30).
Removed
- Dead
bulletin_createdtrigger handler — the event handler that wrote abulletin_boardsection into every branch dashboard is retired: nothing fired the event, itsBULLETINS.central.jsonstore no longer exists, and prax already prunesbulletin_boardas a deprecated section. Archived + unwired from the event registry; prax's pruning stays (td-102). - Dead
backup/run/test dir — leftover from an ad-hoc backup test run (only its generated.backupignorehad been tracked); removed (td-218).
Documentation
- Backup docs corrected —
.backup/is now documented as a shared runtime namespace (@backup stores + @memory rollover safety copies + @flow plan archive), not @backup-exclusive. @backup's README gained full command coverage, the.backup/store layout, and a.backupignore("gitignore for backups") section; its branch prompt's stale.backup_system//drive_test.pynames were fixed. Root README lists @backup and documents.backupignore; the navmap was corrected. The shipped root/.backupignorewas realigned toBUILTIN_IGNORES(dropped stale.backup_system/+ over-broad*logs). @memory and @flow READMEs now cross-reference their.backup/writes, and the orphanedprax/.backupignore(a stale per-branch config) was removed. - Root README agent roster brought current — added the three missing agents
(
@daemon,@skills,@commons) to the tree and tables, and normalized the agent count to 17 everywhere (was an inconsistent mix of "13" and "14").@daemonjoins Quality & operations; a new "Capabilities and community" group covers@skills+@commons(td-28). /prepnow reconciles todos against reality — the session-wrap command (both the Claude.claude/commands/prep.mdand the Codex skill mirror) gained a step to audit every open todo against the actual system (ls/find/git ls-files/grep/audit) and close what's verifiably done — catching todos finished in a past session but never closed.- Backup ignore architecture documented — confirmed and written down the
two-layer model so it stops getting re-discovered:
BUILTIN_IGNORESis the seed that generates a new project's.backupignoreat register and is never consulted at backup time;.backupignore(viaload_spec) is the runtime source of truth. There's no static fallback, so the seed is safety-critical — an empty.backupignorebacks up everything and can crash the machine. Added a "How Ignores Work" README section + code comments onBUILTIN_IGNORESandload_spec. Also addedlogs/to the seed so new projects exclude log directories (e.g. prax.jsonloutput) by default, not just*.logfiles (td-27).
[2026-06-23]
The 2.6.0 release — a large dev → main merge spanning several weeks (68 commits).
Headline changes below; the granular per-merge history is in the dated sections that follow.
Added
- Compass v2 — devpulse-owned SQLite/FTS5 rated-decision engine +
/compasshuman-triggered capture (separate from @memory; DB gitignored). - Decentralized daemon scheduler — each branch owns
.daemon/schedule.json; the daemon discovers and fires. - Telegram skill — the Dev-Pass bridge ported to a self-contained AIPass skill that consumes services as opt-in imports.
- Tiered prompt injection — Tier 0 kernel every turn + Tier 1 navmap by cadence, replacing the single always-on global prompt.
- seedgo
HARDCODED_PATHstandard (#37) — flags hardcoded home paths in source and docstrings.
Changed
- @backup fully restored —
aipass.backup.*namespace, 9-stage Rich CLI, versioned baseline + per-file diff engine, Google Drive sync +restore. - Memory subsystem unified — single-source config limits, char-limit edit-gate, unified entry schema, rollover safety + the silent-rollover repair.
- Legacy global prompt retired across every runtime — Claude (cadence) and Codex (SessionStart) read the same tier files.
- @daemon / @commons / @skills revived to working citizens.
- Public source genericized —
Patrick→user(private memories stay gitignored).
Fixed
- Secrets hardening — no secret value reaches stdout (cleared CodeQL #86-88,
py/clear-text-logging-sensitive-data). - Memory rollover was silently dead — the PreCompact hook now delegates to
drone @memory rollover; the v1 line-count / 600-line fallback removed entirely. - Hardcoded home paths removed (seedgo #37).
@memorysymbolic.pybuilds its 8 dash-encoded branch-path names at runtime (was a literal-home-patrick-);@praxbranch_detector.pydocstrings genericized. Both back to 100%Hardcoded_Path. - Green-CI fixes across Linux / Windows / macOS;
dispatch_monitorPID-429substring bug; git post-merge friction (FF-only realign).
[2026-06-19]
Fixed
aipass initnow seeds the tiered prompts to new projects (@aipass). The init template + bootstrap still handed new projects the retired global prompt with no tiers; now.aipass/project_hooks.jsonmirrors the live wiring (tier0_kernel+navmapenabled,global_promptdisabled) andbootstrap.pyseeds both tier.mdfiles.init updatebackfills existing projects. (77 bootstrap tests, 100% seedgo.)- Cadence reset observability (@hooks).
reset_counter()silently no-op'd when the Claude session id was absent; it now fails loud, logs the session id + prior turn on each reset, falls back to hook data for the id, and handles a corrupt state file. (The post-compaction counter reset was already working — this makes it visible so it can't fail invisibly.) - Memory rollover was silently dead — fixed end-to-end (@hooks + @memory). The
PreCompact rollover hook read its limits from
.trinityfile metadata, but DPLAN-0210 had moved limits into @memory'smemory.config.json— so the hook always fell back to a 600-line check the lean files never reached, and rollover never fired (for weeks). The hook is now a thin trigger delegating todrone @memory rollover check/run;compact.pyreads the current list schema (it was calling.keys()on a now-listkey_learnings). Both fail loud instead of a silent exit-0. - Removed @memory's v1 line-count / 600-line silent fallback entirely. The
detector + extractor are now v2-only (
per_branch→defaults→ warn-and-skip); a parse failure logs loud and skips rather than silently falling back. Deleted_get_max_lines/_load_config/_detect_growing_array/ the line-count extraction path. (959 tests.)
Removed
- Legacy global prompt fully retired across every runtime (DPLAN-0215). After
the tiered cutover the old
global_promptis now gone, not just disabled:global_loader.py+ its tests deleted, theglobal_promptblock stripped from.aipass/hooks.json+project_hooks.json,_resolve_global_prompt+ all global seeding removed fromaipass initbootstrap/update, the cadence default + bypass entries cleaned, and bothaipass_global_prompt.md/project_global_prompt.mdarchived. Claude (cadence) and Codex (SessionStart) now read the same tier files — one prompt source, every runtime.
Added
- seedgo
HARDCODED_PATHstandard (#37). A new checker (hardcoded_path_check.pyhardcoded_path_content.py,test_checkers_batch10.py) flags hardcoded home paths —/home/<user>and dash-encoded-home-<user>-— in source and docstrings, keeping the public repo clean.
[2026-06-18]
Changed
- Prompt injection is now tiered by cadence instead of one 8k always-on block
(FPLAN-0284 / DPLAN-0214). The single global prompt is split into two
cadence-throttled tiers: Tier 0 (
.aipass/tier0_kernel.md, ~2k) injects every turn — identity grounding, thedrone @agent --helpreflex, and the disaster-preventer rules; Tier 1 (.aipass/tier1_navmap.md, ~7.7k) injects every 5th turn plus at session start and right after compaction — the full agent roster, framework, conventions, and a new Terminology section. The hook engine gained per-loader cadence periods; the oldglobal_promptloader is retired (kept as a reference snapshot). Net: more navigation context reaches agents while less is paid per turn. Fresh-clone wiring is seeded fromcadence.pydefaults +setup.sh+provider_manifest.json. - Public source genericized —
Patrick→ genericuser. No personal identifiers in tracked code/docs: the compass decision-source enum (patrick→user) + the/compasscommand, the devpulse local prompt, theaipass initonboarding example (--name Patrick→--name YourName), and stale refs across @ai_mail / @backup / @flow. Private memories (.trinity/, compass DB) keep personal context — they're gitignored. - Telegram skill genericized (@skills). Retired the inactive
patrick_privatepersonal bot from the skill's tests; the message sender now defaults to the Telegram user's first name (fallbackUser) instead of a hardcodedPatrick.
Added
- Prompt-craft conventions harvested from Claude Code's own prompts
(DPLAN-0213). A
Writing voicesection in.aipass/PROMPT_STYLE.md(file_path:linerefs, write-for-a-person, three-tier "where detail lives"); a blast-radius habit in the devpulse prompt; faithful-reporting + no-gold-plating folded into the Tier 0 kernel. - Skill frontmatter discipline (@skills). A
when_to_usefield with trigger phrases (surfaced during discovery scans) and per-step "Done when:" success criteria across the SKILL.md templates. HARDCODED_PATHstandard (@seedgo, 37th checker). Flags absolute home-dir literals in source — POSIX/home/<user>/, macOS/Users/<user>/, Windows user-home paths, and Claude Code's dash-encoded-home-<user>-form — with a bypass for legitimate test fixtures. Swept the repo for violations.prompt_changeflow playbook (PPLAN template). A reusable SOP for changing any injected prompt — leads with "live ≠ seeded" and walks every wiring layer + fresh-install seed path; born from theaipass initseeding gap this surfaced.
[2026-06-16]
Security
- Secrets door hardened — no raw secret value ever reaches stdout
(DPLAN-0211).
@api get-secretpreviously printed retrieved secret values to stdout — an acute exposure in AIPass because Claude Code captures command stdout into the model context. The command now emits a masked summary by default (provider/slug: set (N chars)), writes the raw value only to a0600-mode file via--out FILE(printing just the path), and--listprints slug names only. Thetelegramskill — the sole consumer — was rewired from subprocess-parsingget-secretstdout to the in-process secrets module API. Clears CodeQL clear-text-logging alerts #86/#87/#88.
Fixed
@ai_maildispatch monitor mislabeled failures as "API rate limit" on a PID-429collision. The monitor classifies dispatch failures by substring-scanning the stderr log for"429"/"529", but that log includes the monitor's own header line(PID <pid>). A monitor PID containing"429"(e.g.14290) was read as an HTTP 429, overwriting the real bounce reason (e.g. sandbox-abort-4) with "API rate limit" — and flakingtest_sandbox_failure_sends_bouncedeterministically-by-PID in CI. The scan now excludes the monitor's own---framing lines; genuine429/529markers in agent output are still detected.
[2026-06-15]
Added
- Telegram bridge ported into AIPass as a self-contained skill (FPLAN-0277).
The Dev-Pass Telegram bridge (multi-bot long-poll listener → tmux Claude
injection → Stop-hook reply) is ported AS-WAS into a self-contained
telegramskill that consumes AIPass services instead of bespoke wiring: secrets via the new@api get-secret, logging via@prax, and the outbound Stop hook registered through the@hooksengine. Three phases — P1@apiaddsget-secret <provider/slug> [--json|--list]+auth/secrets.py(reads~/.secrets/aipass/); P2@skillsports the 14-file bridge (~5,300 lines)- ~424 tests into
.aipass/skills/telegram/, rewiring every seam to services; P3@hooksportstelegram_response.py(the reply path, with the 3-layer SubagentStop/sidechain/transcript-cursor defense intact) and registers it on the Stop event. A 366-tag completeness map (TELEGRAM_PORT_MAP.md) audited the port: 288 verified, 23 gaps (top gap — a missing test log-isolation fixture — now fixed), 55 deferred to a live round-trip. Live bring-up (real bot creds, systemd install, telethon auth, message round-trip) is still pending.
- ~424 tests into
[2026-06-13]
Changed
-
Unified memory entry schema — Phase 1 (DPLAN-0207). All four
.trinityentry types (key_learnings,sessions,todos,observations) move to one shape: numbered + dated, list-shaped, newest-first.key_learningsconverts from a dict to a numbered list; the rollover extractor now trims the oldest by number from the tail, and the schema normalizer self-heals ordering by re-sorting onnumber— so an out-of-order write can never archive a fresh entry (the bug surfaced in S229, where rollover ate the newest key_learning instead of the oldest). Backward-compatible: un-migrated dict-shaped key_learnings skip cleanly, no crash. All 17 branches migrated toschema_version3.0.0 (reversible per-file backups, no data loss). A follow-up made the rollover detector and the learnings manager (used by rollover + symbolic) list-aware — a liverollover checkcaught they still counted key_learnings as a dict, so an at-cap list was invisible to the detector (the 955 unit tests stayed green because none counted a list). 960 tests; seedgo 99% (1 pre-existing unused-function on an unwired manager API). Remaining:/memo+/prepand @spawn template updates. -
Memory config relocated to the json-home and unified behind one self-healing loader (FPLAN-0271).
memory.config.jsonmoved from the loose trackedconfig/dir into the gitignoredmemory_json/custom_config/(operator-tunable, fast-access) and.plans_processed.jsonintomemory_json/root; the emptyconfig/dir was removed. The config was previously read by 9 separate loaders, each carrying its own disagreeing defaults (8 divergence classes — incl. the headline bug where a missing config silently flippedentry_limits.enforceoff, plus rollover defaulting to 600 vs the configured 500). All 9 now read through oneapps/handlers/json/config_loader.pywith a singleDEFAULT_CONFIG+ non-mutating deep-merge + self-heal: a missing file is rewritten from code defaults (warn-firstenforce: false), while malformed JSON fails loud and is never overwritten. Deadintakesection deleted; a static_metablock inDEFAULT_CONFIGdocuments each section's consumer files. Code-as-Template: the on-disk file is local tuning, code carries the committed defaults — same model as hookscadence_config.json. Verified: 949 memory tests green, seedgo @memory 100%, live self-heal / malformed-no-clobber / edit_gate checks pass. Design: DPLAN-0206. Follow-up parked: issue #643 (codifycustom_config/as a seedgo standard).
[2026-06-12]
Changed
- Devpulse dashboard slimmed — todos no longer duplicated (startup-context
fix).
DASHBOARD.local.jsonwas embedding the fulltodos[]bodies that already live in.trinity/local.json; since both files are read at every startup, that was pure duplication. The dashboard now emitstodo_countonly (the glance value) — the bodies are commented out in the praxdevpulse_dashboardplugin'stodo_section.py(revivable). DashboardDASHBOARD.local.json6.8 KB → 3.0 KB. Devpulse-only (plugin, not templated). Verified: seedgo 100%, 17/17 plugin tests. - Deprecated dashboard sections are now actually pruned on refresh.
bulletin_board(and the other entries in prax'sDEPRECATED_SECTIONS:devpulse,commons_activity,agent_status,memory_bank) were listed as deprecated but only excluded from template pushes — they lingered in every branch's liveDASHBOARD.local.json. Added_prune_deprecated_sections()to the prax dashboardrefreshpath (reusing the singleDEPRECATED_SECTIONSconstant), so a refresh strips them. Verified:bulletin_boardremoved from the devpulse dashboard; 116/116 prax tests, seedgo 100%. (Follow-up:@triggerstill has abulletin_createdwriter to retire separately.) - Dashboard slimmed to a lean glance — removed duplicated/dead sections.
Dropped three sections from the devpulse dashboard:
session(broken since May — read keysid/d/sumvs the actualsession/date/summary, so it always wrote empty strings — and it duplicatedlocal.json, which loads at startup),todo(carried onlytodo_count, already inquick_status; now sourced directly fromlocal.json), andai_mail(its counts live inquick_status; the section is removed from output after quick_status is computed from it). End state: 4 sections (flow,memory,git,dispatch)- the
quick_statusglance.session_section.py/todo_section.pyarchived (not deleted).DASHBOARD.local.jsonoverall 6.8 KB → 2.4 KB. Verified: seedgo 100%, 108 prax tests. (Follow-up:@ai_mail'sdashboard_sync.pysection writer to retire separately.)
- the
- quick_status now self-sources mail counts from
inbox.json. Decouples the glance from theai_mailsection: prax's three quick_status calculators read.ai_mail.local/inbox.jsondirectly (_read_mail_counts) fornew_mail/opened_mail, so theai_mailsection is no longer a data dependency and can be retired. 116 prax tests, seedgo 100%. - Retired
@ai_mail's dashboard section writer (completes the dashboard slim). ai_mail no longer writes to the dashboard — removedpush_dashboard_updatefrom 5 call sites and archiveddashboard_sync.py. With prax self-sourcing mail counts, theai_mailsection now stays gone (a mail op no longer re-adds it — verified). 737 ai_mail tests. .backupignoreis now a true.gitignorefor the backup system — a single source of truth (FPLAN-0269). Replaced the hand-rolledfnmatch+part-loop matcher (which broke leading-slash anchoring,*-crossing-/, dir-onlyfoo/,!negation, and last-match-wins) with thepathspecgitwildmatch library, so.backupignorehonors full gitignore semantics: include-by-default,!negation,#comments, anchoring, dir-only, last-match-wins.BUILTIN_IGNORESis demoted to a seed-only default (written when the file is absent, never merged at runtime), and the separateIGNORE_EXCEPTIONS/is_exceptionlayer is removed (exceptions are native!lines). Snapshot, versioned,all, and mirror-cleanup now all obey the one file..ruff_cache/+.coverageadded to the default.pathspec(pure-Python, cross-OS) declared. Verified by artifact (seedgo 100%, 220 tests incl. 26 new gitignore-parity tests) + live (a dotfile flows into the store,!negation re-includes end-to-end).- Backup store dir renamed
.backup_system/→.backup/, deadversions/removed (FPLAN-0269 follow-up). The backup root is now.backup/(shorter, coexists with@flow's.backup/processed_plans/); the orphaned per-timestampversions/scaffold and the unusedbuild_versioned_path()— both superseded by the Phase-3versioned/baseline+diff store — are gone. Drive sync confirmed reading.backup/versioned/+.backup/drive_tracker.jsonvia the sharedbackup_root(). Verified by artifact (seedgo 100%, 220 tests) + live (a throwaway project writes to.backup/, noversions/dir).
Fixed
- Backup Drive sync no longer silently drops 41% of files — including the
memories (FPLAN-0269). Removed a foreign dotfile-skip in
drive_sync.pythat excluded every dotted path (.trinity/memories,.chroma/vectors,.aipass/prompts,.ai_mail.local/mailboxes — 4558 files) from the offsite Google Drive copy while the local snapshot/versioned kept them. Drive now uploads the full versioned store (already exactly the.backupignore-filtered set). Added a Drive-sync output panel matching the Snapshot/Versioned stages (header, progress, stats, Duration | Location).
Added
-
Backup Google Drive sync pipeline + restore command (FPLAN-0268, Phase 4 of FPLAN-0264 — final). Faithful port of GOLD's
GoogleDriveSyncagainst the live@apigateway (get_drive_service+api_call_with_retry— never the console-OAuth path). Newhandlers/drive/:DriveClient(folder hierarchyAIPass Backups/<project>/, thread-safe cache, retry-with-rebuild),upload.py(resumableMediaFileUpload, 3 threaded workers),tracker.py(mtime+size dedup → no re-upload of unchanged files),test.py(connectivity). All fourdrive_*modules un-stubbed;allnow runs snapshot→versioned→ drive-sync and fails honestly if Drive creds are absent (never silent-skips, never fakes success, snapshot+versioned still report). Newrestorecommand (restore <project> list <file>/restore <project> file <file> <out>) exposing the Phase-3 baseline+diff restore engine. Drive tests fully mocked — zero real Google calls in CI. Verified by artifact + live: audit 100% (all 37 files), 187 tests, ruff clean, restorelist/fileround-trip confirmed. -
Backup uses the repo-root pyright config like every citizen. Removed backup's standalone
pyrightconfig.json(a leftover from its pre-namespace standalone days, archived) so it inherits the root config — resolving imports consistently with the rest of AIPass. Dead PyQt5ui/settings_window.py(never wired) archived. -
Backup versioned baseline + per-file diff engine (FPLAN-0267, Phase 3 of FPLAN-0264 — the heart). Faithful port of the GOLD versioned engine, replacing the mtime full-copy-into-timestamped-dirs remnant. One persistent store (
.backup_system/versioned/) with GOLD's file-folder packaging: each file gets<parent>/<name>/holding the current copy, a<stem>-baseline-<date>.<ext>full copy from the first run (never touched again), and<name>_diffs/<name>_v<old-mtime>.diffunified-diff patches on every change — append-only, versioned never deletes (cleanup stays snapshot-only). Versioned and snapshot back up the identical file set (same scan + ignore patterns;allshares one scan). Change detection is ledger-free (source mtime vs store-current mtime,copy2-preserved) — kills the regression where running snapshot starved the next versioned via the sharedtimestamps.json. Newdiff/restore.py(list_versions+restore_file);diff/generator.pywired (binary detection + diff include/ignore patterns). +15 tests (125 total). Verified by artifact + live end-to-end: snapshot-first-then-versioned still baselines everything (starvation dead), edit → real diff with old-mtime timestamp, source delete → versioned store untouched while snapshot mirror-deletes, restore round-trip byte-identical. -
Backup snapshot fidelity + shared core (FPLAN-0266, Phase 2 of FPLAN-0264). Restored the snapshot-side machinery the 2026-04-23 rewrite degraded, ported from the GOLD archive onto the current per-project handlers. New
handlers/cleanup/mirror.pycleanup_deleted_files— exception-aware mirror-delete: files removed from source are now removed from the snapshot (was a blindrmtree+recopy), respecting ignore-exceptions.copy/snapshot.pygains mtime-skip (quick-check fast path — unchanged files no longer re-copied), a long-path guard (>260), and read-only handling.report/result.pyBackupResultnow tracks critical vs non-critical errors + warnings +files_deleted;ignore/patterns.pygainsIGNORE_EXCEPTIONS/is_exception(). +16 tests (test_snapshot_fidelity.py, 110 total). Verified by artifact + live: audit 100%, 110 passed, and a real throwaway-project test (delete two files → re-snapshot → both mirror-deleted, kept files preserved, 3 skipped/0 re-copied). -
Backup test suite + seedgo 100% — restoration foundation (FPLAN-0265, Phase 1 of FPLAN-0264). Put a safety net under
backupbefore the feature rebuild: newtests/suite (94 tests — json_handler, CLI routing, filesystem handlers, error resilience, mocked drive) ported from the canonical citizen conftest pattern (hermetic,tmp_path, stdlib-only → 3.10–3.13), driving module coverage to 27%. Standards brought to 100% across all 35: shared--help/-h/helpguard wired into all 10 modules'handle_command(Cli + Introspection), the 6 Phase-3 drive/diff/ui stubs wired-or-bypassed (Dead_Code + Unused_Function),requirements.project.txtadded (Architecture), README module list + the small Modules/Trigger fixes (display.handle_command,create_progress_bar→build_progress_bar). Verified by artifact: re-ran audit (100%) + pytest (94 passed) + ruff (clean).
Fixed
-
Memory rollover no longer silently loses rolled-off learnings ("No embeddings generated"). A capped
.trinityfile rolls its excess entries out to vectors; two combined bugs dropped them on the floor instead. (1) On the "embedding returned empty but success=True" path the orchestrator logged the error and continued — but the source file was already trimmed, so the entry was lost from both the file and ChromaDB; it now restores the pre-trim backup before continuing (fail-honest). (2) A concurrent-rollover race (two runs ~33ms apart) let the second run extract nothing yet still report success → empty embeddings → bug #1;extract_with_metadatanow honors theskippedflag and the orchestrator skips no-op extractions before the embedding stage. Verified by artifact + live: a 25/25-capped test file rolls over → embeds (384-dim) →drone @memory searchreturns it at 91% similarity; audit 100%, 876 tests (+4). -
Backup Google Drive folder duplication + dedup-wipe fixed (GOLD-faithful lock restoration). The Phase-4 port had narrowed
GoogleDriveSync's folder lock: a singledrive_syncrun's 3 upload workers raced the folder search+create → multiple "AIPass Backups" root folders, andget_or_create_backup_folderreset the dedup tracker on every call (re-uploading everything = the slowness). Restored GOLD's structure exactly:get_or_create_project_folder/get_or_create_nested_folderhold_folder_cache_lockacross the entire method (cache + root-ensure + search- create);
get_or_create_backup_folderis lock-free (called inside the project lock — no re-entrant deadlock), short-circuits cached ids via_verify_folder_id, and clears the tracker only on a genuine brand-new root folder. Also: all fourdrive_*commands route by their underscore names (were hyphenated → "Unknown command");requirements.project.txtnow declares the three google libs. Verified by artifact (seedgo 100%, 197 tests incl. a 5-thread concurrency test → exactly one create) + live (real Drive backup: no duplicate folders).
- create);
-
Backup rich CLI output restored end-to-end (FPLAN-0263 + drone passthrough).
drone @backup snapshot|versioned|allrendered a flat text block instead of the original rich output. Two independent causes, both closed: (1) the rich rendering was never carried forward in backup's revival — rebuilt as a faithful 9-stage port (newbackup_timestampsstate handler +display.pypipeline: Last-backups panel → boxed header → live Rich progress bar → result summary → Backups-now panel;BackupResultextended withfiles_checked/files_skipped/backup_path; copy handlers emiton_progresscallbacks). (2) drone was flattening it at the pipe —@backupran throughcapture_output=True(non-TTY → Rich strips color, thetransientprogress bar renders to nothing) and the 30s capture timeout would kill large backups; addedbackupto drone'sINTERACTIVE_BRANCHESso all@backupcommands inherit the terminal (mirrorscli). Verified live under a pty: full color- animated progress bar.
[2026-06-11]
Fixed
- seedgo audit local↔CI parity (FPLAN-0261). The local
seedgoaudit could silently diverge from CI, breaking the "pass locally first, then ship" gate. Three independent causes, all closed without coupling any checker to git (.gitignoreis git's concern, not the audit's): (1) usage-scanning checkers (unused_function,dead_code, +4)rglob'd gitignored output dirs (artifacts/,dropbox/), so a stray local file could mark a function "used" that a clean checkout correctly flags — every per-checker skip list hoisted to one sharedSOURCE_SKIP_DIRS(output dirs simply aren't source). (2) Thediagnosticsstandard shelled out to barepython3 -m pyright(system python, no pyright) and, on the resulting JSON-parse failure, returned 0 errors = clean — a silent false-green; now usessys.executableand fails loud. (3) pyright resolved imports against PATH-python, so results flipped with.venvactivation — pinned via--pythonpath sys.executable. The audit is now deterministic local == CI (proven all-13-branches-100% in an unactivated shell). Also:dronebypasses the test-only brokerstart_background(intentional API, not dead code). - windows-setup CI: guard Linux-only sandbox tests. The kernel-sandbox build
is Linux-only (bwrap,
AF_UNIXsockets,openat2); the code already guards onsys.platform, but four test surfaces ran unconditionally and failed onwindows-latest. Module-levelpytestmark = pytest.mark.skipif(sys.platform != "linux", …)ondrone/tests/test_broker.pyandhooks/tests/test_sandbox.py; scoped guards on the remainingAF_UNIXbroker-socket tests —ai_mail/.../test_dispatch_monitor.py::TestBrokerRealE2E(class) andaipass/.../test_sandbox_check.py::TestCheckBrokerAlivesocket-connect tests (method-level, so the graceful no-broker paths still run on Windows). All skip on Windows and run unchanged on Linux. windows-setup was green pre-sandbox-merge (00edd8b) and red since (0b4ba63); this closes it. - Broker
start_backgroundconnect-before-bind race.drone's out-of-sandbox broker daemon started viastart_background(), which returned before theAF_UNIXsocket was bound — callers then raced the bind, and on a slower machinecreate_identified_connection()hitFileNotFoundError(socket not yet present). Deterministic locally (test_delete_nested_file0/5), green in CI only by timing luck — latent flakiness. Fixed with athreading.Eventset right afterlisten();start_background(timeout=5.0)now blocks on it and raises if the socket never binds, so callers never guess asleep. Removed the 4 blindtime.sleep(0.15)waits from the broker tests. Verified 55/55 broker tests, formerly-failing test 10/10.
Added
-
aipass initdetects missing Claude Code. Stage 6 (CLI choice) now checksshutil.which("claude")when the picked CLI is Claude Code. If absent: interactive runs promptInstall now? [Y/n]and run the canonical installer on yes (nativeclaude.ai/install.sh, PowerShell on Windows,npmfallback, 300s timeout, loud on failure); non-interactive runs warn and continue. The whole system routes through Claude Code (hook bridge, dispatch, prompt injection), so init no longer silently assumes the runtime is present. Only fires when the chosen CLI isclaude. -
Kernel filesystem boundary for agent containment (DPLAN-0202 / FPLAN-0250). Every autonomous agent can now launch inside a kernel-enforced mount namespace (
@anthropic-ai/sandbox-runtime→ bwrap+seccomp) where reads stay fully open (the shared live filesystem is preserved — a bind-mount, not isolation: own-tree writes land live on the real FS instantly) but deletes/overwrites of protected paths (.git, sibling branch trees) fail at the kernel no matter how the call is phrased —rm,python os.remove,find -delete, Write tool all hit EROFS./tmpand the agent's own tree stay writable;.gitis RW for devpulse, RO for builders. A per-role policy generator (@hooks build_policy) derives each branch's writable/RO map from its passport. Privileged deletes route through an out-of-sandbox drone-broker daemon: identity-scoped allowlist,openat2RESOLVE_BENEATH path re-resolution (confused-deputy proof), HMAC identity handshake over a pre-connected inherited fd, JSONL audit.aipass doctorgained a Sandbox check group (bwrap present+functional, node, srt, rg, broker socket) that is LOUD when the flag is on and a prereq is missing — never a silent unsandboxed launch. Proven by a live 16-check red-team suite. Inert by default — gated behindAIPASS_SANDBOX_ENABLED(off); flag-off is byte-identical to the old dispatch path. -
rm_gate demoted to guardrail. Now framed honestly as early-feedback that catches the accidental
rm -rfand teachesdrone rm— belt-and-suspenders, with the kernel sandbox as the actual filesystem boundary. -
Prompt-injection cadence — fire the big loaders every Nth turn. The global and branch prompts are large and were re-injected on every turn even though a prior copy stays in the conversation. They now fire together every 5th turn (config-tunable via
hooks_json/custom_config/cadence_config.json), with a per-session turn counter that resets on a new session and after compaction so context is always rebuilt when it's actually needed. Identity and the mail flag stay every-turn (tiny, want freshness). Cuts recurring per-turn context cost. -
Hook fire/skip observability. Cadence emits a structured
[HOOKS] cadence fired|skipped loader= turn= period= offset=line; the prax monitor renders hook events distinctly so the cadence is visible live. -
Slim global prompt — context-on-demand. The always-injected global prompt was rewritten from a ~13.8KB encyclopedia into a ~7.8KB navigation map (DPLAN-0201):
dronepinned as the router, the framework tree, all 13 agents as short bios, and one drilled reflex — rundrone @agent --helpbefore using a branch. Detail now lives in each agent's--help, fetched on demand. This also dissolves the harness ~10k-char truncation that was silently dropping the old prompt's tail; the slim prompt injects whole. Backup retained alongside.
Changed
- Shared leaf library re-homed:
aipass.common→aipass.aipass.shared(FPLAN-0260).src/aipass/common/was the only non-citizen directory in the agent namespace — a shared lib (json_handler / json_ops / registry_discovery, extracted in TDPLAN-0006 P2) parked as a sibling to the agents with no owner. Per @seedgo design review it now lives inside its steward atsrc/aipass/aipass/shared/, owned by @aipass; @spawn imports across (same blessed shared-infra category asaipass.prax/aipass.cli). Content byte-identical; ~9 import/doc sites updated across aipass+spawn. A new subprocess guard test pins the bootstrap-safety invariant: importingshared/loads zero branch dependencies, soaipass initkeeps working pre-drone on fresh machines. Note:aipass.commonshipped in the v2.5.2 wheel; it was internal plumbing — no deprecation shim. - Action-gated hook sound. Piper now speaks only when a hook actually does
something — handlers return a
soundkey the engine plays, instead of announcing on every invocation. Skipped loaders are silent. Quieter and honest. - README: hardcoded metrics → live badges + qualitative. Version is now a live PyPI badge, test/PR counts replaced with a codecov coverage badge (75% minimum) and qualitative wording — no more stale numbers to hand-maintain.
Fixed
- Cadence counter separate-process race. Each
UserPromptSubmithook runs as its own OS process, so a module-level turn cache double-incremented and the loaders leapfrogged (firing erratically instead of together). Fixed with an mtime debounce + transcript-size token +flockso the counter advances exactly once per real turn, verified against the live execution model. auto_fixran no diagnostics. A leftoverspeak()call (its import removed in the sound refactor) raisedNameErroron every edit, swallowed by the handler's broadexcept— so auto-fix silently surfaced nothing on any.py/.jsonedit. Removed the dead call; diagnostics run again.- Hook events never colored in the monitor. The prax log-watcher's
_HOOK_PATTERNrequired anaction=field that cadence never emits (it logs the action as the bare second word,fired/skipped), so extraction failed and events fell through to plain rendering instead of the styled bold-green ⚡ / dim · treatment. Fixed the regex to capture the bare action word and enriched the event detail (period, offset, short session id).
Security
- Least-privilege token on the
e2e-wheelworkflow.e2e-wheel.ymlwas the one CI workflow missing a top-levelpermissions:block (it was added during the cross-OS work after PR #624 hardened the others), so it ran with the default broadGITHUB_TOKENscopes — dropping the OpenSSF Scorecard Token-Permissions check to 0. Addedpermissions: contents: read; the workflow only reads the repo to build and smoke-test the wheel. - Signed GitHub Releases via Sigstore (keyless). The release workflow now
signs the built wheel + sdist with
sigstore/gh-action-sigstore-python(keyless OIDC — no signing key is generated, stored, or held by anyone) and attaches the resulting.sigstore.jsonbundles to the GitHub Release. PyPI uploads were already attested via Trusted Publishing; this extends verifiable provenance to artifacts pulled from GitHub Releases and satisfies the OpenSSF Scorecard Signed-Releases check. First proof lands on the nextv*tag.
[2026-06-02]
Fixed
aipass initscaffold correctness. A freshaipass initnow generates a project-specificAGENTS.md(newagents_md()generator) instead of falling back to copying AIPass's own repo-rootAGENTS.mdboilerplate — Codex users were getting the wrong file. ProjectREADME.mdquick-start/structure paths now reflect the realsrc/<package>/<agent>/layout.- First-agent default name
my-agent→my_agent.aipass initseeded its default agent with a hyphen, the lone source of a long-standing dir-vs-module mismatch (the directory kept the hyphen while the importable module,@addressand registry name all normalize to underscore). Defaulting tomy_agentmakes directory, module,@addressand the README example all consistent. - Dead
citizenship.registry_pathremoved from spawn templates. The field pointed at a non-existent.aipass/registry.json; it was never read anywhere (registry is located byfind_registry()glob), so it's dropped from thebuilderandbirthrightpassport templates.
Removed
- The entire STATUS flow is decommissioned (TDPLAN-0007). The per-branch
hand-maintained
STATUS.local.mdbeacon and the auto-aggregated centralSTATUS.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+/memoskill, the compact-recovery hook, the email footer, andaipass init/ spawn scaffolding. Live branch state was already fully covered byDASHBOARD.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 operationaltodos[]section in.trinity/local.json(@memory-owned schema, capped, never vectorized by rollover), pushed to all 13 branches and surfaced as atodo_counton 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.
Wrapped
print_introspection()output in Rich markup across ai_mail, drone, spawn, trigger, prax and devpulse (the rest were already compliant) — presentation only, no logic change — sodrone @branchwith no args renders consistent styled output everywhere. - CLI polish for human-facing output.
drone @hooks --helprewritten (Rich, withhooksound on/off/statusnow surfaced);drone @spawnrepair help clarified as distinct fromupdateand showing the preview/--applyflow; drone restores Rich colour on human-facing routed output (--help, introspection,status) via the inherit path. - Spawn backups land in one namespace
.spawn/.recovery/(TDPLAN-0006 P4). Spawn's pre-merge JSON backups previously dropped a.recovery/directory at each branch root (which had accumulated 242 stale auto-generatedDASHBOARDbackups across 10 branches).aipass.common.json_ops.backup_jsongained an optionalbackup_dirparameter (default unchanged), and spawn's update engine now directs backups to{branch}/.spawn/.recovery/— tucked under the spawn-managed.spawn/dir instead of cluttering the branch root. Memory stays in the safety net (the engine simply never touches.trinity//DASHBOARDon update, so it never needs to back them up). Stale.recovery/backups cleaned up. (315 tests, seedgo 100%.) - No more cross-branch engine imports —
aipass init updatecalls spawn via subprocess (TDPLAN-0006 P3).init_flow.pypreviously didfrom aipass.spawn.apps.modules.sync_registry import sync_registry— the one place aipass reached directly into spawn's Python. Replaced with a subprocess call to the already-existingdrone @spawn sync-registry --fix(same pattern asaipass init agent→drone @spawn create), preserving graceful degradation (a missingdrone, non-zero exit, or timeout is silently skipped — registry sync never hard-fails an update). The aipass branch now has zero direct imports of another branch's engine code; the remaining cross-branch imports are shared service layers only (cli Rich UI, prax logging, trigger events). (438 tests, seedgo 100%.) aipass.commonshared library — dedup spawn/aipass scaffold machinery (TDPLAN-0006 P2).@spawnand@aipasseach carried their own copy of the JSON merge/handler utilities and registry discovery. Extracted them into a new branch-free packagesrc/aipass/common/(json_ops=deep_merge+backup_json;json_handler.JsonHandler;registry_discovery.find_registry) that both branches now import.aipass.commonimports zero branch code, soaipass/bootstrap.py(which runs before the drone runtime exists) can depend on it without breaking the pre-infrastructure constraint. The duplicated copies are deleted (spawn keeps a thin re-export shim; aipass'sjson_handlershrank 254 → 88 lines). Thesave_jsoncontract is unified to raiseValueErroron invalid structure across both branches. (313 spawn + 434 aipass tests, both seedgo 100%.)
Fixed
- Flow plan-type self-serve UX — register override, help, orphan cleanup.
Explicit
drone @flow register <dir> <PREFIX>now overrides an auto-derived prefix instead of silently failing (guarded — refuses if the auto-registered type already holds plans), so custom prefixes are settable when adding a new plan type.create/templates --helprewritten to dynamically list registered types + templates and document the add-a-new-type workflow. Stale orphan plan registries removed; deadprefix_exists()dropped. (728 tests, seedgo 100%.) drone @spawn updateno longer scrambles branches (#636, critical — TDPLAN-0006 P0+P1). The update engine compared a freshly-created branch against the class template by content hash with rename-detection, and because the CREATE path regenerated template-registry IDs in filesystem-walk order (≠ the master's hand-crafted IDs), a branch created seconds earlier produced 30 proposed renames that rotated identity/memory dirs into each other (apps→.trinity→.seedgo→.claude→.archive→.aipass), turnedREADMEintoDASHBOARD, and deep-merged stale template into live.trinity/memory —update <class> --allwould have destroyed every citizen in one command. Rebuiltupdate_ops.py(v2.0) on an explicit named-managed-files + path-based model:.trinity/*,DASHBOARD.local.json,artifacts/birth_certificate.jsonand.seedgo/bypass.jsonare delivered on create only and never touched on update; the create==update invariant now yields 0 renames / 0 merges on a fresh branch. The old ID-based engine (change_detection.py,reconcile.py) is deleted.- Destructive spawn ops are now dry-run by default (TDPLAN-0006 P0).
drone @spawn updateanddrone @spawn repairpreview by default and require an explicit--applyto write — forgetting a flag is now a safe no-op instead of irreversible damage (--dry-runkept as an alias).aipass doctorrepair suggestions emit the matching--applyform.
Added
- Introspection Rich-formatting standard (seedgo). New
check_introspection_rich_formattingchecker enforces that each branch'sprint_introspection()output uses Rich markup (delegation-aware — it walks_-prefixed helper functions), keeping no-argdrone @branchoutput styled and consistent. Documented inintrospection.md; all 13 branches brought into compliance (see Changed). - Playbook plan type (
PBPLAN) — reusable SOP checklists (flow). A newplaybook_planstemplate family for throwaway, vectorize-on-close operational runbooks (first SOP: the Sunday merge). Drop a.mdundertemplates/playbook_plans/, register once, thendrone @flow create . "subject" <sop>stamps a run to tick through and close. - Memory-pool auto-processing (TDPLAN-0005) — dropped files in
memory/memory_pool/are now vectorized and archived automatically on session-start and pre-compact, instead of requiring a manualdrone @memory pool process. A 3-branch build:@memorygains an intake handler +poolmodule (processes then empties the pool,keep_recent=0),@hooksadds alifecycle/auto_processhandler (session-guarded viaCLAUDE_CODE_SESSION_ID, since Claude Code has no SessionStart hook), and@triggergains event #15 (memory_pool_auto_processed) with a Medic error path. Runtime pool dirs (memory_pool/,memory_pool_archive/) are now gitignored. - HVTracker badge added to the README badge cluster, linking to the public agent profile at hvtracker.net (closes #628).
git_gateread-verb allowlist — raw read-only git for every branch. The PreToolUsegit_gatepreviously blocked all raw git (forcingdrone @giteven for harmless reads), which left agents unable to inspect what git ships — the exact forensics needed to diagnose the audit gap above. It now allows 22 read-only verbs raw (ls-files,ls-tree,show,cat-file,rev-parse,rev-list,log,status,diff,blame,archive,grep, …) while write operations staydrone-gated. Global options (-C,-c,--git-dir, …) are skipped when extracting the verb, and chained commands are split on&&/||/;/|so a read piped into a write still blocks the whole line. (81 tests)- Cross-OS end-to-end WIRING test (
tests/e2e/,e2e-wheel.yml) — the first CI gate that proves real AIPass wiring (not units-with-mocks) by building the wheel, installing it into a clean venv, and asserting a 4-tier ladder: package install + console scripts (T0),aipass initscaffolding (T1), a hook actually firing via the bridge with an observableengine.jsonlrecord (T2a), anddroneresolving + subprocess-executing a real branch (T3). Runs on a 3-OS matrix (ubuntu/windows/macos,fail-fast: false). Ran red-first on Windows by design and immediately earned its keep — it caught two real, previously uncovered Windows wiring bugs (aipass initpreflight +dronestdout encoding, both fixed below). Notably the layers we most feared — clean-wheel install (T0) and hook firing (T2a) — passed on Windows. (DPLAN-0194 / FPLAN-0239) drone rm— provider-agnostic safe delete — a contained recursive delete that lets agents clean up scratch dirs without tripping therm -rfblock. Deletes are confined to the project root and the system temp dirs (/tmpand$TMPDIR), refusing anything outside (home,/etc,/, etc.). Even inside those roots it hard-refuses protected internals —.git,.trinity/,.aipass/,.codex/,.agents/, and sibling-branch worktrees — mirroring the filesystem boundary an OS-sandboxed agent (e.g. Codex) enforces, so behavior is consistent across CLIs. Pure-Python (shutil.rmtree), with a red-team test suite for containment escapes (symlinks, traversal, sibling branches). (#630)rm_gatehook — block raw recursiverm, teach the safe path — a PreToolUse gate (mirroringgit_gate) that blocks rawrm -r/-rf/-fr/--recursiveand redirects the agent todrone rm. Provider-agnostic (runs in the hook engine, not tied to Claude Code permission rules), conservative (unparseable targets are blocked, not allowed), and skipsdrone rmitself. This makes the safe-delete path discoverable at the moment of friction. (#630)- Hook engine logs
agent_type/agent_idper fire — the engine now records which agent triggered each hook (e.g.agent=mainvsagent=Explore) in bothengine.jsonland the prax monitor stream. Previously the payload flowed into handlers but was never logged, leaving no way to tell an internal main-turn fire from a real sub-agent fire. Pure visibility; no behavior change. Groundwork for #606. (#606) - OpenSSF Best Practices passing badge — AIPass earned the OpenSSF Best
Practices (CII) passing badge (100% of criteria), added to the README badge
cluster. Self-certified across all six categories — basics, change control,
reporting, quality, security, and analysis. Complements the existing OpenSSF
Scorecard, lifting the
CII-Best-Practicescheck from 0. (DPLAN-0193)
Changed
-
Standards floor raised to genuine 100% across all 13 branches — completed the campaign that lifted the seedgo gate threshold from 80 to 100. Rather than bypass failing files, two check flaws were fixed at the root: (1) the file-size / architecture check is now advisory (warn-only for 700–1500 line files with no docstring nudge, hard-fail only above 1500) — large files are a smell, not a defect; (2) readme-freshness now compares against git history, not file mtime —
git checkout/mergereset mtimes without any semantic change, so the old check false-positived (flow + prax shared an identical mtime from one git event, not real edits). It now diffs the README's "Last Updated" against the last commit that touched.py. Genuine content fixes where warranted (aipass requirements template + handler routing; honest README content refreshes on flow, prax, devpulse). The readme-freshness failure message now teaches the right fix ("update README content, then set the date — don't just bump it"). Also optimized the devpulse watchdog poll cadence (2s → 5s; the loop is cheap, so the tighter interval was wasted CPU). (#631) -
Retired the blanket
rmdeny from provider settings —setup.shandaipass initno longer shipBash(rm -rf*)/Bash(rm -r *)deny rules (they were mis-filed among git rules, blocked all/tmpcleanup, and gave a bare "permission denied" with no guidance). Therm_gatehook +drone rmnow own this — cross-provider, path-aware, and they teach.aipass doctordetects the stale rules on existing installs andaipass doctor --fixremoves them (idempotent, preserves all other rules). Claude Code still natively circuit-breaksrm -rf /andrm -rf ~. (#630)
Fixed
Windows Test/macOS Testare no longer path-filtered — they were stalling PRs as required checks. Both workflows only triggered whensetup.sh/drone/cli.py/handlers/__init__.py/pyproject.tomlchanged, but branch protection listswindows-setup/macos-setupas required. On any PR that didn't touch those paths the workflows never ran, so GitHub parked the required checks as "Expected — waiting for status" indefinitely, blocking the merge (the tests themselves were green — they simply didn't fire). They now run on every push/PR to main/dev, like the other required lanes. (A required check must never be path-filtered.)seedgo-auditCI gate was red despite 100% local audits — four checkers validated the working tree instead of committed source. CI audits a cleangit checkout(tracked files only — git ships no empty or gitignored dirs), but the working tree carries runtime dirs (logs/,*_json/,artifacts/,.trinity/,passport.json), so every branch scored ~97% in CI while passing at 100% locally. Reproduced exactly with a tracked-only tree (git archive HEADaudits to CI's 97%). Four checkers now measure what git actually ships:log_structureno longer fails when the gitignoredlogs/dir is absent (it still enforces no-hardcoded-paths);readmecross-references.gitignore(viagit check-ignorewith a fallback list) and skips gitignored dirs/links in the directory-tree and dead-link checks;encapsulationinfers the branch from the path when the gitignoredAIPASS_REGISTRY.jsonis unavailable (and no longer collides on theaipassbranch);architectureskips cleanly when the gitignoredpassport.jsonis absent. A follow-up refinedreadme'sgit check-ignoreuse:.gitignoredir-only patterns (trailing slash —logs/,**/*_json/,.trinity/) don't match a clean checkout's non-existent paths unless directory intent is signalled, so the check now also tests the trailing-slash form (this was the last 1% —readmeflaggedcli_json/logs/artifactsas "missing on disk" in CI only). The CI gate (.github/scripts/seedgo_audit.py) now also prints the failing standards and their check messages, so a sub-100 result says why, not just the percentage. Finally, theseedgo-auditCI job now installs thememoryextra (pip install -e ".[dev,memory]"): thediagnosticsstandard runs pyright over every branch, and memory's handlers importchromadb/numpyat module level — without those declared deps installed, pyright reported them as unresolved (reportMissingImports=error) and memory scored 55%, a false failure from a missing CI dep rather than a code defect. Clean-tree and working-tree audits both report 13/13 = 100%. (DPLAN-0195)- Two latent Windows portability bugs caught by the new e2e harness — both
were always present in the code; they only surfaced now because this is the
first CI to run
aipass initscaffolding and real-branchdronerouting on Windows (the old Windows CI ran an editable install,aipass-less, and only routed to in-process modules, so both paths had zero Windows coverage). Pure portability fixes — Linux/macOS behaviour is unchanged.aipass initcrashed on Windows (surfaced as a misleading "Unknown command: init"). Init scaffolded the project correctly, then crashed printing its✓ Project initializedbanner — Rich wrote the ✓/box glyphs through a cp1252 stdout, raisingUnicodeEncodeError ('charmap'); the error handler's✗message hit the same wall, bubbling up to the command router which mislabeled it. Theaipassentry point now reconfigures stdout/stderr to UTF-8 in place on Windows. (The init preflight ancestor-walk was also hardened to skip un-enumerable Windows drive-root entries — defensive, not the trigger.)drone @branchcrashed on Windows with the sameUnicodeEncodeError ('charmap').droneresolved + subprocessed the branch correctly, then crashed printing the captured output through cp1252 stdout. The existingPYTHONUTF8guard only affected child interpreters, not the live process streams —drone's entry point now alsoreconfigure()s stdout/stderr to UTF-8 in place.- CI unit lane no longer runs the e2e wheel tests.
ci.yml'spytest --rootdir=.swept intests/e2e/(which build a wheel per the dedicatede2e-wheel.yml), failing the unit lane; it now--ignores them. (DPLAN-0194)
- A release merge can no longer destroy the
devbranch —drone @git mergepassed--delete-branchtogh pr mergeunconditionally, so merging adev→mainPR deleted the persistentdevbranch on the remote and stranded the working tree onmain(the next commit silently landing on main). Merge now looks up the PR's head ref and only deletes non-protected branches —devandmainare never deleted, and an undeterminable head ref fails safe (no delete). After a merge it returns the working tree todev(loud warning if it can't).drone @git branchesnow runsfetch --prunebefore listing so it reflects the live remote instead of stale cached refs, and a newdrone @git prune-tempcleans up merged temp PR branches. (#625) drone @git status/diffshow their scope — when scoped to a branch (no--all), output now appends "(showing scope — use --all for full repo)", so an empty scoped view is no longer mistaken for a clean repo. (#623)- External projects can call AIPass branches via drone —
drone @api ...(and anydrone @X) now resolves from a non-AIPass project CWD instead of being blocked with "path escapes project root." The resolver was validating a branch's path against the primary registry root even when the branch was found via theAIPASS_HOMEfallback, so any external project (Vera Studio, Daemon) hit a false security block.resolve_branch()now validates containment against the registry the branch was actually found in. Security is unchanged — each branch is still contained within its own declaring registry's root; genuine path escapes remain blocked. (#618) aipass <command>runs instead of printing an introspection banner —aipassis a user-facing binary, soaipass doctor(and every other command) must execute, not describe itself. All 7 modules (doctor,doctor_fix,doctor_wire,handoff,help_chat,init_flow,profile) previously hit a no-args→introspection gate (a standard meant fordrone @branch <module>discovery) and showed a banner on bare invocation. Now bare invocation runs the command or shows usage; the introspection banner moved to--info. The seedgo introspection standard is bypassed for these binary-invoked modules (documented).- Dashboard plan counts no longer zeroed on refresh — a branch's
active_planswas reset to0by everydrone @prax dashboard refresh, becausePLANS.central.jsononly held Flow's own plans (location==FLOW_ROOTfilter). The central file is now comprehensive: all plans grouped per-branch, so refresh reports each branch's real count (e.g. devpulse now shows its 12 open plans instead of 0).
Security
dependency-scan(pip-audit) green again — upgrade pip, drop stale ignores. TheSecurity Scanworkflow'sdependency-scanjob had gone red: pip-audit scans the whole environment, and the runner's bundled pip (26.1.1) carries advisory PYSEC-2026-196 (fixed in 26.1.2). The job now runspython -m pip install --upgrade pipbefore auditing (it was the only CI job not upgrading pip), removing the vulnerable version outright rather than suppressing it. 26.1.2 also resolves CVE-2026-3219 and CVE-2026-6357, so the two now-stale--ignore-vulnentries were removed — verified against a clean reproduction of the job's environment, which audits to "No known vulnerabilities found" with nothing ignored.- Pinned the
requestsfloor to a non-vulnerable version — raisedrequeststo>=2.34.2inpyproject.tomland the API branch'srequirements.project.txt(which previously listed it unconstrained). This clears six OSV advisories the OpenSSF Scorecard flagged against the dependency (PYSEC-2014-13, PYSEC-2014-14, PYSEC-2018-28, GHSA-9wx4-h78v-vm56, GHSA-9hjg-9r4m-mvj7, GHSA-gc5v-m9x4-r6x2) — the oldest surfaced only because the dependency was declared without a version bound. No runtime change (the AIPass venv already ran a fixed release). (DPLAN-0193) - Pinned the test container base image by digest —
Dockerfile.testnow pinsubuntu:24.04to its registry digest (sha256:786a8b55…) so the test image is reproducible and tamper-evident, clearing the ScorecardcontainerImage not pinned by hashfinding. (DPLAN-0193)
[2026-05-30]
Added
drone @hooks status— read-only viewer for a project's hook config: master switch, every hook's enabled state per event group, matchers, and an enabled/total summary. Resolves the project's.aipass/hooks.jsonby walking up from CWD. (DPLAN-0190 Phase B)- Hooks activate in every project —
aipass initnow writes.aipass/hooks.json, so new projects fire the hook engine out of the box (previously: no config shipped, 0 hooks fired).aipass init updateunion-merges the template, preserving any per-hook on/off choices the user made.aipass doctornow checks for the config's presence. Dead hook-script shipping (_ship_hooks) removed. (DPLAN-0190 Phase A) - README logo — centered logo image replaces plain
# AIPassheader. Newassets/logo.pngadded to the repo. - OpenSSF Scorecard —
.github/workflows/scorecard.ymlruns the official OSSF Scorecard action on push tomainand weekly. Publishes a public security health score at scorecard.dev with a README badge. Actions pinned by SHA. - GitHub Releases —
publish.ymlnow cuts a GitHub Release on eachv*tag, with notes pulled from the top CHANGELOG section and the built dist attached. PyPI publish + GitHub Release now fire from the same tag. - Registry descriptions — all 13 branches now have one-liner descriptions
in
AIPASS_REGISTRY.json.drone systemsshows what each agent does instead of blank lines. Closes #607.
Changed
- Security gates fully project-aware — both the edit gate and the
subagent stop gate now derive the package name dynamically from CWD instead
of hardcoding
src/aipass/. Cross-branch write protection and branch detection work for anysrc/<package>/<branch>/project; previously the subagent gate silently no-opped outside AIPass. 9 new external-project tests. Closes #605. - Hooks branch promoted to service — registry profile changed from
"AIPass Workshop" to "library" so it appears in
drone systemsalongside the other 12 services. - Hooks branch hardened to 100% seedgo — the @hooks citizen took full
ownership of its branch: every handler verified wired + firing, README
rewritten (two-tier provider/project model, dynamic-dispatch design, event
table), 2 stale tests resolved (253 pass). Dead-code/unused-function flags
documented as architectural bypasses — the 15 handlers are invoked
dynamically via
importlibfromhooks.jsonpaths, never statically imported. (DPLAN-0191)
Release
- Version 2.5.0 published to PyPI. Trusted publishing via GitHub Actions
(
publish.ymltriggers onv*tags — no manual twine upload needed). The same tag now also cuts a GitHub Release with these notes attached.
Removed
- Gemini CLI full removal — deleted
.gemini/directory (5 files) andGEMINI.md. Stripped all references fromsetup.sh(~50 lines),README.md,bug-report.yml,aipass init(bootstrap/scaffold/test), hooks (README/prompt/passport), and prax monitoring (~300 lines). 21 files changed, -927 lines. Closes #608.
[2026-05-25]
First weekly release. AIPass now follows a Sunday release cadence: changes
accumulate on dev throughout the week and merge to main as a single
versioned release with notes.
Added
- Hook engine — a new centralized dispatch system for all hook execution. A thin bridge receives events from the AI provider (Claude, Codex, etc.) and routes them through a single Python engine that reads per-project configuration, executes the appropriate handlers, and logs every invocation. Replaces 14 standalone shell/Python scripts with native handler modules organized by domain: prompt injection, security enforcement, lifecycle management, and notifications.
- Per-project hook configuration via
.aipass/hooks.json. Each project can enable, disable, or customize individual hooks without touching provider-level settings. Previously hooks fired globally with no per-project control. - Audio feedback on hook events using Piper TTS. All 14 handlers
produce distinct spoken audio cues so operators can monitor sessions
without watching the terminal. A shared sound module
(
hooks/apps/sound.py) providesspeak()andplay()with built-in mute support. Toggle withdrone @hooks hooksound on|off— muting silences all 14 handlers without skipping their functional logic. - Hooks agent — the 13th citizen in the AIPass registry, owning all hook infrastructure: the engine, bridge, handlers, and configuration schema.
- Dashboard plugin for devpulse — aggregates git status, session history, and dispatch state into a single startup view. Wired into the session startup protocol so branch managers see current state immediately.
- External log routing — prax now accepts structured log entries from any branch, not just its own modules. Hook executions, dispatches, and agent activity all flow into the central monitoring log.
Changed
- Provider settings fully migrated to bridge pattern. All hook entries in the Claude provider configuration now call the bridge dispatcher instead of individual scripts. Each hook produces its own system-reminder to the model, preserving prompt injection fidelity (a single merged bridge was found to break prompt delivery due to Claude Code's output persistence threshold).
- setup.sh rewritten to install hooks via the bridge pattern. The old version hardcoded 14 script paths; the new version writes a single bridge call per event type and validates that the bridge module exists.
- Documentation sweep across
.claude/README.md,SECURITY.md, the global prompt, and branch-level docs to reflect the new hook architecture. References to legacy.claude/hooks/scripts replaced with the native handler locations. aipass init updatenow correctly preserves user-customized hook settings during project updates instead of overwriting them.- Seedgo snapshot tests rebuilt — the provider hooks snapshot fixture and extraction logic were structurally broken (silently passing with zero results). Both the fixture format and the test assertions have been corrected.
- Test suite updated for hook migration —
test_git_gate.pyimports from the new handler module;test_bootstrap.pyno longer asserts that project initialization ships standalone hook scripts (it no longer does).
Fixed
- Settings merge on project update —
aipass init updatewas clobbering user hook configurations. The merge logic now layers AIPass defaults under existing user settings. - Python 3.10 test collision — a module/function name collision caused mock patch targets to fail on Python 3.10. Test targets corrected.
- Dead code removal — removed an unused CLI
__main__.pyentrypoint and cleaned up.gitignoreentries that were masking tracked files. - Codecov patch threshold lowered to 50% to reflect the project's current coverage baseline and stop false-negative CI failures.
Removed
- 18 standalone hook scripts in
.claude/hooks/disabled (renamed with(disabled)suffix). Their logic now lives in native handler modules undersrc/aipass/hooks/apps/handlers/. The old files remain on disk for reference but are no longer executed. drone hook-soundsplugin disabled. Sound control moved to hooks branch asdrone @hooks hooksound on|offwith full mute support for all 14 handlers (the old plugin only controlled 4).
Infrastructure
- Provider manifest migrated to bridge pattern.
provider_manifest.jsonnow stores bridge commands ($AIPASS_HOME/...bridges/claude.py EventType) instead of standalone script names.doctor_wire.pyauto-wires bridge entries directly — no longer copies scripts to~/.claude/hooks/or generatessys.executablepaths. Doctor checks validate commands exist in provider settings instead of checking for script files on disk. - README v3 — rewritten for external users. Tighter problem/solution framing, collapsible agent details, Gemini CLI removed (untested), user-project perspective throughout.
- Inline handoff (
aipass init runStep 11) — new default stays in the current terminal viaos.execvpinstead of opening a new window. Users choose "stay here" or "new window." Enables single-terminal demo recordings. Closes #610. - Project-aware global prompt — the global prompt loader now detects
whether CWD is inside AIPass or an external project. External projects
receive their own lighter prompt (from
.aipass/aipass_global_prompt.md) instead of the full AIPass-internal playbook. Fixesdrone @praxerrors in new projects. - Project CLAUDE.md template —
aipass initnow generates a project-specific CLAUDE.md from.aipass/project_CLAUDE.mdinstead of copying the AIPass-internal one. Removes the startup protocol reference todrone @prax dashboard refreshwhich doesn't exist in external projects. - Gemini CLI removed from
aipass initCLI choices and handoff options. GEMINI.md no longer created for new projects. Gemini CLI is being retired upstream. - Demo GIF added to
assets/demo.gifand referenced in README.
This is the first CHANGELOG entry. Prior work is documented in the repository's commit history and branch session logs.