Files
AIPass/CHANGELOG.md
T

676 lines
42 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Changelog
All notable changes to AIPass will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
Entries are grouped by merge under a dated section header (`YYYY-MM-DD`). Package
releases follow [SemVer](https://semver.org/) and are tracked by the git tag and
PyPI version — not the changelog header.
---
## [2026-06-11]
### Fixed
- **seedgo audit local↔CI parity (FPLAN-0261).** The local `seedgo` audit could
silently diverge from CI, breaking the "pass locally first, then ship" gate.
Three independent causes, all closed without coupling any checker to git
(`.gitignore` is 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 shared `SOURCE_SKIP_DIRS` (output dirs simply aren't source). (2) The
`diagnostics` standard shelled out to bare `python3 -m pyright` (system python,
no pyright) and, on the resulting JSON-parse failure, returned *0 errors = clean*
— a silent false-green; now uses `sys.executable` and **fails loud**. (3) pyright
resolved imports against PATH-python, so results flipped with `.venv` activation
— pinned via `--pythonpath sys.executable`. The audit is now deterministic
local == CI (proven all-13-branches-100% in an unactivated shell). Also: `drone`
bypasses the test-only broker `start_background` (intentional API, not dead code).
- **windows-setup CI: guard Linux-only sandbox tests.** The kernel-sandbox build
is Linux-only (bwrap, `AF_UNIX` sockets, `openat2`); the code already guards on
`sys.platform`, but four test surfaces ran unconditionally and failed on
`windows-latest`. Module-level `pytestmark = pytest.mark.skipif(sys.platform !=
"linux", …)` on `drone/tests/test_broker.py` and `hooks/tests/test_sandbox.py`;
scoped guards on the remaining `AF_UNIX` broker-socket tests —
`ai_mail/.../test_dispatch_monitor.py::TestBrokerRealE2E` (class) and
`aipass/.../test_sandbox_check.py::TestCheckBrokerAlive` socket-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_background` connect-before-bind race.** `drone`'s out-of-sandbox
broker daemon started via `start_background()`, which returned *before* the
`AF_UNIX` socket was bound — callers then raced the bind, and on a slower machine
`create_identified_connection()` hit `FileNotFoundError` (socket not yet present).
Deterministic locally (`test_delete_nested_file` 0/5), green in CI only by timing
luck — latent flakiness. Fixed with a `threading.Event` set right after `listen()`;
`start_background(timeout=5.0)` now blocks on it and **raises** if the socket never
binds, so callers never guess a `sleep`. Removed the 4 blind `time.sleep(0.15)`
waits from the broker tests. Verified 55/55 broker tests, formerly-failing test 10/10.
### Added
- **`aipass init` detects missing Claude Code.** Stage 6 (CLI choice) now checks
`shutil.which("claude")` when the picked CLI is Claude Code. If absent: interactive
runs prompt `Install now? [Y/n]` and run the canonical installer on yes (native
`claude.ai/install.sh`, PowerShell on Windows, `npm` fallback, 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 is `claude`.
- **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.
`/tmp` and the agent's own tree stay writable; `.git` is 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, `openat2`
RESOLVE_BENEATH path re-resolution (confused-deputy proof), HMAC identity handshake
over a pre-connected inherited fd, JSONL audit. `aipass doctor` gained 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 behind
`AIPASS_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 -rf` and teaches `drone 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): `drone` pinned as the router, the framework tree, all 13 agents
as short bios, and one drilled reflex — run `drone @agent --help` before 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 at `src/aipass/aipass/shared/`,
owned by @aipass; @spawn imports across (same blessed shared-infra category as
`aipass.prax`/`aipass.cli`). Content byte-identical; ~9 import/doc sites updated
across aipass+spawn. A new subprocess guard test pins the bootstrap-safety
invariant: importing `shared/` loads zero branch dependencies, so `aipass init`
keeps working pre-drone on fresh machines. Note: `aipass.common` shipped 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 `sound` key 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 `UserPromptSubmit` hook 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 + `flock` so the counter advances exactly
once per real turn, verified against the live execution model.
- **`auto_fix` ran no diagnostics.** A leftover `speak()` call (its import removed
in the sound refactor) raised `NameError` on every edit, swallowed by the
handler's broad `except` — so auto-fix silently surfaced nothing on any
`.py`/`.json` edit. Removed the dead call; diagnostics run again.
- **Hook events never colored in the monitor.** The prax log-watcher's
`_HOOK_PATTERN` required an `action=` 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-wheel` workflow.** `e2e-wheel.yml` was the
one CI workflow missing a top-level `permissions:` block (it was added during
the cross-OS work after PR #624 hardened the others), so it ran with the
default broad `GITHUB_TOKEN` scopes — dropping the OpenSSF Scorecard
Token-Permissions check to 0. Added `permissions: 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.json` bundles 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 next `v*` tag.
---
## [2026-06-02]
### Fixed
- **`aipass init` scaffold correctness.** A fresh `aipass init` now generates a
project-specific `AGENTS.md` (new `agents_md()` generator) instead of falling
back to copying AIPass's own repo-root `AGENTS.md` boilerplate — Codex users
were getting the wrong file. Project `README.md` quick-start/structure paths
now reflect the real `src/<package>/<agent>/` layout.
- **First-agent default name `my-agent` → `my_agent`.** `aipass init` seeded 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, `@address`
and registry name all normalize to underscore). Defaulting to `my_agent` makes
directory, module, `@address` and the README example all consistent.
- **Dead `citizenship.registry_path` removed from spawn templates.** The field
pointed at a non-existent `.aipass/registry.json`; it was never read anywhere
(registry is located by `find_registry()` glob), so it's dropped from the
`builder` and `birthright` passport templates.
### 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.**
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 — so `drone @branch` with no args renders
consistent styled output everywhere.
- **CLI polish for human-facing output.** `drone @hooks --help` rewritten (Rich,
with `hooksound on/off/status` now surfaced); `drone @spawn` repair help
clarified as distinct from `update` and showing the preview/`--apply` flow;
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-generated `DASHBOARD`
backups across 10 branches). `aipass.common.json_ops.backup_json` gained an
optional `backup_dir` parameter (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/`/`DASHBOARD` on
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 update` calls spawn via
subprocess (TDPLAN-0006 P3).** `init_flow.py` previously did
`from 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-existing `drone @spawn sync-registry --fix` (same pattern as
`aipass init agent` → `drone @spawn create`), preserving graceful degradation
(a missing `drone`, 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.common` shared library — dedup spawn/aipass scaffold machinery
(TDPLAN-0006 P2).** `@spawn` and `@aipass` each carried their own copy of the
JSON merge/handler utilities and registry discovery. Extracted them into a new
branch-free package `src/aipass/common/` (`json_ops` = `deep_merge` +
`backup_json`; `json_handler.JsonHandler`; `registry_discovery.find_registry`)
that both branches now import. `aipass.common` imports **zero** branch code, so
`aipass/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's `json_handler` shrank
254 → 88 lines). The `save_json` contract is unified to **raise `ValueError`**
on 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 --help` rewritten to dynamically list registered
types + templates and document the add-a-new-type workflow. Stale orphan plan
registries removed; dead `prefix_exists()` dropped. (728 tests, seedgo 100%.)
- **`drone @spawn update` no 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`), turned `README` into
`DASHBOARD`, and deep-merged stale template into live `.trinity/` memory —
`update <class> --all` would have destroyed every citizen in one command. Rebuilt
`update_ops.py` (v2.0) on an explicit **named-managed-files + path-based** model:
`.trinity/*`, `DASHBOARD.local.json`, `artifacts/birth_certificate.json` and
`.seedgo/bypass.json` are 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
update` and `drone @spawn repair` preview by default and require an explicit
`--apply` to write — forgetting a flag is now a safe no-op instead of irreversible
damage (`--dry-run` kept as an alias). `aipass doctor` repair suggestions emit the
matching `--apply` form.
### Added
- **Introspection Rich-formatting standard (seedgo).** New
`check_introspection_rich_formatting` checker enforces that each branch's
`print_introspection()` output uses Rich markup (delegation-aware — it walks
`_`-prefixed helper functions), keeping no-arg `drone @branch` output styled and
consistent. Documented in `introspection.md`; all 13 branches brought into
compliance (see Changed).
- **Playbook plan type (`PBPLAN`) — reusable SOP checklists (flow).** A new
`playbook_plans` template family for throwaway, vectorize-on-close operational
runbooks (first SOP: the Sunday merge). Drop a `.md` under
`templates/playbook_plans/`, register once, then
`drone @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 manual
`drone @memory pool process`. A 3-branch build: `@memory` gains an intake
handler + `pool` module (processes then empties the pool, `keep_recent=0`),
`@hooks` adds a `lifecycle/auto_process` handler (session-guarded via
`CLAUDE_CODE_SESSION_ID`, since Claude Code has no SessionStart hook), and
`@trigger` gains 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_gate` read-verb allowlist — raw read-only git for every branch.** The
PreToolUse `git_gate` previously blocked *all* raw git (forcing `drone @git`
even 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 stay `drone`-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 init` scaffolding (T1), a hook actually
firing via the bridge with an observable `engine.jsonl` record (T2a), and
`drone` resolving + 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 init` preflight + `drone` stdout
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 the `rm -rf` block.
Deletes are confined to the project root and the system temp dirs (`/tmp` and
`$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_gate` hook — block raw recursive `rm`, teach the safe path** — a
PreToolUse gate (mirroring `git_gate`) that blocks raw `rm -r`/`-rf`/`-fr`/
`--recursive` and redirects the agent to `drone rm`. Provider-agnostic (runs in
the hook engine, not tied to Claude Code permission rules), conservative
(unparseable targets are blocked, not allowed), and skips `drone rm` itself.
This makes the safe-delete path discoverable at the moment of friction. (#630)
- **Hook engine logs `agent_type` / `agent_id` per fire** — the engine now
records which agent triggered each hook (e.g. `agent=main` vs `agent=Explore`)
in both `engine.jsonl` and 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-Practices` check 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`/`merge` reset 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 `rm` deny from provider settings** — `setup.sh` and
`aipass init` no longer ship `Bash(rm -rf*)` / `Bash(rm -r *)` deny rules
(they were mis-filed among git rules, blocked all `/tmp` cleanup, and gave a
bare "permission denied" with no guidance). The `rm_gate` hook + `drone rm`
now own this — cross-provider, path-aware, and they teach. `aipass doctor`
detects the stale rules on existing installs and `aipass doctor --fix` removes
them (idempotent, preserves all other rules). Claude Code still natively
circuit-breaks `rm -rf /` and `rm -rf ~`. (#630)
### Fixed
- **`Windows Test` / `macOS Test` are no longer path-filtered — they were
stalling PRs as required checks.** Both workflows only triggered when
`setup.sh`/`drone/cli.py`/`handlers/__init__.py`/`pyproject.toml` changed, but
branch protection lists `windows-setup`/`macos-setup` as *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-audit` CI gate was red despite 100% local audits — four checkers
validated the working tree instead of committed source.** CI audits a clean
`git 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 HEAD`
audits to CI's 97%). Four checkers now measure what git actually ships:
`log_structure` no longer fails when the gitignored `logs/` dir is absent (it
still enforces no-hardcoded-paths); `readme` cross-references `.gitignore`
(via `git check-ignore` with a fallback list) and skips gitignored dirs/links
in the directory-tree and dead-link checks; `encapsulation` infers the branch
from the path when the gitignored `AIPASS_REGISTRY.json` is unavailable (and no
longer collides on the `aipass` branch); `architecture` skips cleanly when the
gitignored `passport.json` is absent. A follow-up refined `readme`'s
`git check-ignore` use: `.gitignore` dir-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% — `readme` flagged
`cli_json`/`logs`/`artifacts` as "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, the `seedgo-audit` CI job now installs the `memory` extra
(`pip install -e ".[dev,memory]"`): the `diagnostics` standard runs pyright over
every branch, and memory's handlers import `chromadb`/`numpy` at 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 init` scaffolding and real-branch `drone` routing 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 init` crashed on Windows (surfaced as a misleading "Unknown
command: init").** Init scaffolded the project correctly, then crashed
*printing its `✓ Project initialized` banner* — Rich wrote the ✓/box glyphs
through a cp1252 stdout, raising `UnicodeEncodeError ('charmap')`; the error
handler's `✗` message hit the same wall, bubbling up to the command router
which mislabeled it. The `aipass` entry 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 @branch` crashed on Windows with the same `UnicodeEncodeError
('charmap')`.** `drone` resolved + subprocessed the branch correctly, then
crashed *printing* the captured output through cp1252 stdout. The existing
`PYTHONUTF8` guard only affected child interpreters, not the live process
streams — `drone`'s entry point now also `reconfigure()`s stdout/stderr to
UTF-8 in place.
- **CI unit lane no longer runs the e2e wheel tests.** `ci.yml`'s
`pytest --rootdir=.` swept in `tests/e2e/` (which build a wheel per the
dedicated `e2e-wheel.yml`), failing the unit lane; it now `--ignore`s them.
(DPLAN-0194)
- **A release merge can no longer destroy the `dev` branch** — `drone @git merge`
passed `--delete-branch` to `gh pr merge` unconditionally, so merging a
`dev`→`main` PR deleted the persistent `dev` branch on the remote and stranded
the working tree on `main` (the next commit silently landing on main). Merge now
looks up the PR's head ref and **only deletes non-protected branches** — `dev`
and `main` are never deleted, and an undeterminable head ref fails safe (no
delete). After a merge it returns the working tree to `dev` (loud warning if it
can't). `drone @git branches` now runs `fetch --prune` before listing so it
reflects the live remote instead of stale cached refs, and a new
`drone @git prune-temp` cleans up merged temp PR branches. (#625)
- **`drone @git status`/`diff` show their scope** — when scoped to a branch (no
`--all`), output now appends "(showing <branch> 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 any `drone @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 the `AIPASS_HOME` fallback, 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** —
`aipass` is a user-facing binary, so `aipass 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 for `drone @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_plans` was reset to `0` by every `drone @prax dashboard refresh`, because
`PLANS.central.json` only held Flow's own plans (`location==FLOW_ROOT` filter).
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.**
The `Security Scan` workflow's `dependency-scan` job 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 runs
`python -m pip install --upgrade pip` before 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-vuln` entries were removed — verified against a clean
reproduction of the job's environment, which audits to "No known
vulnerabilities found" with nothing ignored.
- **Pinned the `requests` floor to a non-vulnerable version** — raised
`requests` to `>=2.34.2` in `pyproject.toml` and the API branch's
`requirements.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.test` now pins
`ubuntu:24.04` to its registry digest (`sha256:786a8b55…`) so the test image is
reproducible and tamper-evident, clearing the Scorecard `containerImage not
pinned by hash` finding. (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.json` by walking
up from CWD. (DPLAN-0190 Phase B)
- **Hooks activate in every project** — `aipass init` now writes
`.aipass/hooks.json`, so new projects fire the hook engine out of the box
(previously: no config shipped, 0 hooks fired). `aipass init update`
union-merges the template, preserving any per-hook on/off choices the user
made. `aipass doctor` now checks for the config's presence. Dead hook-script
shipping (`_ship_hooks`) removed. (DPLAN-0190 Phase A)
- **README logo** — centered logo image replaces plain `# AIPass` header.
New `assets/logo.png` added to the repo.
- **OpenSSF Scorecard** — `.github/workflows/scorecard.yml` runs the official
OSSF Scorecard action on push to `main` and weekly. Publishes a public security
health score at scorecard.dev with a README badge. Actions pinned by SHA.
- **GitHub Releases** — `publish.yml` now cuts a GitHub Release on each `v*` 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 systems` shows what each agent does
instead of blank lines. Closes [#607](https://github.com/AIOSAI/AIPass/issues/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 any `src/<package>/<branch>/` project; previously the
subagent gate silently no-opped outside AIPass. 9 new external-project tests.
Closes [#605](https://github.com/AIOSAI/AIPass/issues/605).
- **Hooks branch promoted to service** — registry profile changed from
"AIPass Workshop" to "library" so it appears in `drone systems` alongside
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 `importlib` from `hooks.json` paths, never statically
imported. (DPLAN-0191)
### Release
- **Version 2.5.0** published to PyPI. Trusted publishing via GitHub Actions
(`publish.yml` triggers on `v*` 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) and
`GEMINI.md`. Stripped all references from `setup.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](https://github.com/AIOSAI/AIPass/issues/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`) provides `speak()` and `play()` with built-in
mute support. Toggle with `drone @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 update`** now 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.py` imports
from the new handler module; `test_bootstrap.py` no longer asserts that
project initialization ships standalone hook scripts (it no longer does).
### Fixed
- **Settings merge on project update** — `aipass init update` was
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__.py` entrypoint
and cleaned up `.gitignore` entries 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
under `src/aipass/hooks/apps/handlers/`. The old files remain on disk for
reference but are no longer executed.
- **`drone hook-sounds` plugin** disabled. Sound control moved to hooks
branch as `drone @hooks hooksound on|off` with full mute support for
all 14 handlers (the old plugin only controlled 4).
### Infrastructure
- **Provider manifest migrated to bridge pattern.** `provider_manifest.json`
now stores bridge commands (`$AIPASS_HOME/...bridges/claude.py EventType`)
instead of standalone script names. `doctor_wire.py` auto-wires bridge
entries directly — no longer copies scripts to `~/.claude/hooks/` or
generates `sys.executable` paths. 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 run` Step 11) — new default stays in the
current terminal via `os.execvp` instead of opening a new window. Users
choose "stay here" or "new window." Enables single-terminal demo
recordings. Closes [#610](https://github.com/AIOSAI/AIPass/issues/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. Fixes `drone @prax` errors
in new projects.
- **Project CLAUDE.md template** — `aipass init` now generates a
project-specific CLAUDE.md from `.aipass/project_CLAUDE.md` instead of
copying the AIPass-internal one. Removes the startup protocol reference
to `drone @prax dashboard refresh` which doesn't exist in external
projects.
- **Gemini CLI removed** from `aipass init` CLI choices and handoff
options. GEMINI.md no longer created for new projects. Gemini CLI is
being retired upstream.
- **Demo GIF** added to `assets/demo.gif` and referenced in README.
---
*This is the first CHANGELOG entry. Prior work is documented in the
repository's commit history and branch session logs.*