3013 lines
190 KiB
Markdown
3013 lines
190 KiB
Markdown
# 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-07-18]
|
||
|
||
**docs** — root README v3 restructure (DPLAN-0249): single-funnel story with
|
||
zero duplicated commands (install, `aipass new`/`init run`, trees, drone
|
||
examples each taught exactly once), hero link line to aipass.ai/PyPI/r/AIPass,
|
||
three reserved gif slots. Positioning ruling: the README tells only the
|
||
Claude Code on Linux/WSL story — Codex/macOS/Windows mentions and the Roadmap
|
||
section removed (code support unchanged; Docker distribution will serve those
|
||
users later). Earlier same day: stale demo.gif embed dropped (#701) and
|
||
aipass.ai realigned to the v2.7.3 front door.
|
||
|
||
**v2.7.3** — the onboarding chain: from `git clone` to a conversation with an
|
||
agent that remembers you. Install's three dead-ends are gone — the default
|
||
`init` path, headless runs, and `aipass new` all now end where they should:
|
||
`install` chains through the guided init and **opens a live conversation with
|
||
the AIPass concierge**, first prompt authored with the install report in its
|
||
context. The concierge's Welcome Mode (research-backed opener, one name-ask,
|
||
deferred setup triage, hooks-first health check via a real @hooks dispatch,
|
||
every suggestion with its exact command) was proven in a live multi-turn
|
||
door-test — including the second-session payoff: relaunch, and it picks up
|
||
mid-task where you left off. Plus `aipass new` and the front-door overhaul below.
|
||
|
||
### Added (onboarding chain — TDPLAN-0014)
|
||
|
||
- **Install→chat handoff**: after init returns, install `launch_inline`s the
|
||
concierge with an authored first prompt (fresh-install recognition + binary
|
||
report). TTY-only; headless returns cleanly.
|
||
- **Welcome Mode** in the concierge branch prompt: capability opener with 3–5
|
||
concrete starters, single graceful name-ask, ~turn-5 setup push ("every
|
||
machine is different"), hooks-first verification incl. trust-registry
|
||
enrollment, setup plan seeded from the cross-OS checklist, Windows→WSL
|
||
recommendation, prax-monitor + hooksound tips, exact copy-paste command with
|
||
every suggestion.
|
||
- **Feedback pulse** (@hooks): one ignorable line every ~10 turns with the repo
|
||
feedback link — `aipass feedback on/off` (alias for `drone @hooks feedback`)
|
||
turns it off. Registered disabled for the AIPass host itself. 25 tests.
|
||
- **Dead-end kills**: empty-template init now runs handoff + report stages
|
||
(default path ends in the conversation); non-interactive installs complete
|
||
with defaults and exit 0 (headless stage 9 prints the launch command instead
|
||
of spawning); `aipass new` auto-launches into the new project's manager agent
|
||
on a TTY with a printed fallback and Ctrl-C escape line.
|
||
- **Unified handoff prompt**: one `INIT_PROMPT` constant (was two drifting
|
||
strings in init_flow vs handoff).
|
||
|
||
### Fixed (onboarding chain)
|
||
|
||
- Non-TTY `aipass init run` crashed with EOFError at the first prompt (caught
|
||
in a live door-test after unit suites ran green — the prompt layer now
|
||
auto-detects non-TTY and takes defaults).
|
||
- `aipass new` outside an AIPass environment now exits 1 instead of 0.
|
||
- Empty-template handoff messaging no longer claims an agent exists
|
||
("Your project is ready", resolved absolute path instead of `cd .`).
|
||
- Stage numbering shows a skip notice instead of silently jumping 5→8.
|
||
|
||
## [2026-07-17]
|
||
|
||
**v2.7.3 (first pass)** — `aipass new` and the front-door overhaul. The `projects/`
|
||
directory is now a first-class playground: `aipass new <name>` creates a fully
|
||
isolated project — own registry, own git repo with a birth commit, born
|
||
deployable — with a full framework resident agent that answers
|
||
`drone @<name>` from inside the project while staying invisible to the host
|
||
roster. ai_mail enforces the project boundary (cross-project mail is refused
|
||
with a pointer to the feedback channel). A live door-test of the aipass CLI
|
||
exposed a blind spot in the audit — perfect structural scores on an unusable
|
||
front door — so seedgo grew two user-facing-quality standards (`cli_ux`,
|
||
`readme_quality`) and a fleet-100 campaign brought every branch's help output
|
||
and README to the house pattern: 17/17 branches at 100%.
|
||
|
||
### Added
|
||
|
||
- **`aipass new <name>`** (module + handler): creates `projects/<name>` with
|
||
registry-first credential linkage (`registry.metadata.id ==
|
||
passport.citizenship.registry_id`), empty/python templates, interactive
|
||
template + agent prompts (flags for scripted use), a full framework agent
|
||
(entry point, modules/handlers skeleton, trinity set, mailbox, tier files),
|
||
git init + birth commit, and next-step output. 49 tests.
|
||
- **seedgo standards 41–42**: `cli_ux` (8 AST checks — two-tier help, Rich
|
||
console, styled title, purpose line, --help pointer, Usage, Examples, no
|
||
exposed internal plumbing) and `readme_quality` (Quick Start with runnable
|
||
code block, stranger accessibility, invoke/entry-point match, early
|
||
what-description). 36 tests + case-resolution regression tests.
|
||
- **ai_mail cross-project boundary**: sender and recipient project roots
|
||
compared on delivery; cross-project sends refused with a feedback-channel
|
||
pointer. Fail-open for internal/unregistered sends. 13 tests.
|
||
- **Root `.gitignore`**: `projects/*` ignored (each project is its own repo);
|
||
only the future catalog README stays trackable.
|
||
|
||
### Added (second pass — the agent becomes a real citizen)
|
||
|
||
- **`aipass new` agents are now spawn-issued full citizens** (FPLAN-0334): the
|
||
hand-rolled scaffold in the new_project handler is retired for a
|
||
`spawn_agent()` call against @spawn's new `project_agent` template — branch
|
||
prompt, structured mailbox, birth certificate, trinity trio, dashboard,
|
||
house-pattern entry point, and a branch-style README. One authority issues
|
||
citizens; project agents inherit template evolution for free.
|
||
- **Agent home = `src/<project>/<agent>/`**, mirroring the host's
|
||
`src/aipass/<branch>` layout (door-test ruling: the project root is never an
|
||
agent home). Seat paths are relative like host seats; the registry walk stops
|
||
at the first project registry. The first agent is the project's **manager**
|
||
(`citizen_class: manager` — its devpulse), named after the project.
|
||
- **Birth-commit hygiene**: the `.venv` symlink (absolute host path) and the
|
||
registry lock file are no longer tracked in new projects' birth commits.
|
||
- **Boundary verified live in all four directions**: host↔project email and
|
||
dispatch all refused — project→host lands on the ai_mail cross-project check
|
||
with its feedback-channel pointer, closing the leak found in the S319
|
||
prototype probes.
|
||
|
||
### Changed
|
||
|
||
- **aipass front door rebuilt**: `--help` now follows the house pattern with a
|
||
curated command list, usage, and examples (internal plumbing hidden —
|
||
`doctor_fix`/`doctor_wire` renamed underscore-private); `aipass help` shows
|
||
the Q&A screen instead of falling through to the module dump; README
|
||
rewritten to pass the stranger test with a Quick Start and the correct
|
||
invocation.
|
||
- **Fleet-100 sweep**: 15 branches gained Quick Start READMEs and/or
|
||
Usage/Examples help sections — each owner fixed their own front against the
|
||
new gate.
|
||
- **Debug_Print detector hardened**: the regex-based checker matched `print(`
|
||
inside string literals (flagging cli_ux_check's own error messages — the
|
||
auditor was the last branch under 100%). String content is now stripped
|
||
before matching, with a regression test; plus a depth-5 nesting refactor in
|
||
the same file.
|
||
|
||
**v2.7.2** — everything merged since v2.7.1, headlined by the compass decision
|
||
engine v2: curation with supersedes links + write-time conflict advisories
|
||
(Track 1), and ambient recall — rated past decisions now surface verbatim into
|
||
live sessions on matching prompts, governed by session caps and spacing
|
||
(Track 2). Also in this release: the plan close pipeline completes itself
|
||
(auto-vectorization + crash-safe registry writes), drone's 3-layer subprocess
|
||
timeout policy with a collision-free `--drone-timeout` flag, plan-number memory
|
||
search that pins the exact plan, a fleet-wide seedgo 100% restoration, and
|
||
SSH-signed commits now verifying on GitHub. Details in the sections below
|
||
(2026-07-16 carries the full stories).
|
||
|
||
### Changed
|
||
|
||
- **Release cadence ruling: every dev→main merge ships a PATCH bump + tag by
|
||
default.** PyPI tracks main, always current; version numbers carry no
|
||
significance during beta — the big jump is reserved for beta exit.
|
||
|
||
- **Merge playbook SOP refined from live run PPLAN-0010.** The raw
|
||
`git fetch origin main:main` step (now blocked by the git gate) is replaced
|
||
with `drone @git sync` in both places it appeared, and the template opens
|
||
with the exact create command (`drone @flow create . "Merge summary" merge
|
||
pplan` — template name before type), closing the trap where the wrong arg
|
||
order silently stamps the default template.
|
||
|
||
## [2026-07-16]
|
||
|
||
### Added
|
||
|
||
- **Compass ambient recall — Track 2 (DPLAN-0246/FPLAN-0332): rated decisions
|
||
surface unprompted.** On every user prompt, a new hooks handler
|
||
(`compass_recall`, registered in `.aipass/hooks.json` only) queries compass
|
||
FTS with the prompt text and injects matching rulings VERBATIM —
|
||
`[BAD] #56: <decision text>` — tidbits, never vibes. Three branches, one
|
||
pipeline, each piece behind a modules/-boundary API: devpulse's
|
||
`recall_decisions()` (side-effect-free scored candidates; rare-token
|
||
evidence scoring + a query-side stopword filter so greeting/filler words
|
||
can't fake relevance) + `mark_surfaced()` (counts only real injections);
|
||
@memory's pure `should_surface()` governance (promoted from the dormant
|
||
symbolic engine: threshold, 5/session cap, 10-message spacing — first
|
||
surface exempt, 300s cooldown, dedup; state-in/state-out, caller persists);
|
||
@hooks' 90-line handler + engine per-handler budget (errors never block a
|
||
prompt — `compass_recall_unreachable` log signature for @trigger's watcher).
|
||
Live acceptance matrix through the real bridge: topic-with-history prompts
|
||
recall the right ruling (a CI prompt surfaced the red-CI-never-parked
|
||
ruling), small talk and greetings stay silent, repeat prompts gate on
|
||
spacing. Review caught and fixed pre-ship: wrong payload key (`userInput` →
|
||
`prompt`), phantom `CLAUDE_CODE_SESSION_ID` env (session id is
|
||
stdin-payload-only), spacing gate blocking the first surface, and a trust
|
||
registry re-enrollment gap that silently disabled ALL project hooks for 20
|
||
minutes after the hooks.json edit. 446 devpulse + 1011 memory + 1129 hooks
|
||
tests green; seedgo 31/31 on every touched module.
|
||
|
||
- **Compass curation v2 Track 1 (DPLAN-0246/FPLAN-0331): supersedes links +
|
||
write-time conflict check.** A correcting compass entry now archives and
|
||
links what it replaces in one transaction (`compass add --supersedes N`);
|
||
query renders both directions ("supersedes #N" / "ARCHIVED — superseded by
|
||
#M") so a retracted decision can never masquerade as current truth. Every
|
||
`compass add` FTS-checks the new text against active entries and prints a
|
||
non-blocking "possible conflict with #X" advisory — flag-and-ask, no LLM, no
|
||
auto-resolve (boardroom ruling). New `compass note <id>` command (FTS
|
||
re-index proven by test), `--include-archived` query flag (the avoid-list is
|
||
finally searchable), dead `score` column removed from all code surfaces
|
||
(kept inert on disk — zero migration risk). Idempotent PRAGMA-checked
|
||
migration ran clean on the production store (128 rows, no loss); the four
|
||
fresh-eyes-audit archive pairs got their links backfilled. /prep now runs
|
||
one `compass review` per session — curation living in a path that already
|
||
runs, the lesson of all three compass eras. 435 devpulse tests green,
|
||
seedgo 31/31 on both touched modules.
|
||
|
||
- **Close pipeline completes itself (DPLAN-0245): auto-vectorization +
|
||
crash-safe registry writes + drone timeout policy.** Closing a plan now
|
||
produces all side effects from one command — `post_close_runner` invokes
|
||
@memory's plan intake directly after archival (detached, loud on failure,
|
||
drains any backlog it finds), so plans can no longer silently pile up
|
||
unvectorized. Plan registry saves (@flow `save_registry` + mbank
|
||
`save_flow_registry`) now use the O_EXCL lockfile + atomic
|
||
tempfile-and-replace pattern, closing the same lost-update race class fixed
|
||
earlier in CLOSED_PLANS. @drone gained a 3-layer timeout policy: per-command
|
||
overrides (`@memory process-plans` 120s, `@flow close` 90s), a `--timeout N`
|
||
flag, 30s default — replacing the flat 30s guillotine that killed legitimate
|
||
long commands mid-pipeline; the timeout error now says how to override.
|
||
Proven end-to-end live: one `drone @flow close` on a throwaway plan yielded
|
||
archive + vectors + ledger + registry with zero manual steps, and the
|
||
auto-trigger swept a pre-existing backlog file on its first run. 730 flow +
|
||
878 drone tests green, seedgo 100%.
|
||
|
||
### Fixed
|
||
|
||
- **CI seedgo gate back to 100% across all 17 branches.** The Track 2 compass
|
||
recall code left three branches at 99%: @hooks' compass_recall handler was
|
||
missing json_handler operation logging and had two silent catches (now
|
||
logged); @memory's governance module held its implementation in modules/
|
||
(moved to handlers/governance/engine.py with modules/governance.py as the
|
||
thin re-export — the cross-branch import path is unchanged and live-E2E
|
||
verified through the real bridge); devpulse's README test count had drifted
|
||
(309 → 348). Audits re-run per branch: 100% overall, all suites green.
|
||
|
||
- **Plan-number memory search hits the exact plan.** Searching a plan ID
|
||
('DPLAN-0244', 'fplan 0332' — any case, dash or space) now pins the exact
|
||
plan as the top result at 100%, via a metadata lookup on the vector store's
|
||
source-file field instead of embedding similarity (which treats all plan IDs
|
||
as near-identical strings and never surfaced the target). Patrick ruling:
|
||
searching a plan number must return that plan first. Semantic search quality
|
||
for normal queries is unchanged. Also purged 193 junk vectors — throwaway
|
||
probe/flaky test plans from scratchpad sessions (dv4 batch, probe_test_plan,
|
||
throwaway_e2e_proof) that had leaked into the store. 1011 memory tests green.
|
||
|
||
- **drone --timeout collision: router flag swallowed module flags.** The
|
||
DPLAN-0245 subprocess-timeout flag consumed the first `--timeout` token
|
||
anywhere in argv, so module-level flags silently vanished — watchdog's
|
||
`--timeout 1800` never arrived and long watches died at the 600s default
|
||
(live repro x2). Drone's flag is now namespaced `--drone-timeout`; plain
|
||
`--timeout` passes through untouched to the target module, with a regression
|
||
test pinning the passthrough. Per-command overrides intact. 879 drone tests
|
||
green, seedgo 100%.
|
||
|
||
- **@memory command routing eaten by the new governance module.** The
|
||
governance module shipped in Track 2 had the wrong `handle_command`
|
||
signature (`args: list` instead of `command: str, args: list`) and always
|
||
returned True, so auto-discovery routed EVERY @memory command through it
|
||
first — `drone @memory search` answered "governance: unknown command 's'".
|
||
Fixed to the standard signature returning False for commands not its own;
|
||
search verified live (135 results). Library modules must decline commands
|
||
they don't own or they silently hijack the whole CLI. 1011 memory tests
|
||
green, seedgo 31/31.
|
||
|
||
## [2026-07-15]
|
||
|
||
### Fixed
|
||
|
||
- **Plan vectorization pipeline unwedged (DPLAN-0245): 57 closed plans were
|
||
silently missing from semantic memory since mid-June.** Vector IDs were pure
|
||
content hashes, so identical template boilerplate across different plans
|
||
produced duplicate IDs within one ChromaDB upsert — the store rejected the
|
||
entire batch, and the all-or-nothing intake retried the same failing batch
|
||
forever. Fixed in @memory: IDs are now salted with the source filename when
|
||
present (rollover hashes unchanged — no re-vectorization churn), in-batch
|
||
dedup as a safety net, and `process_plans()` now runs per-file with the
|
||
manifest saved after each success so a poison file can never wedge the queue
|
||
again. Backlog drained and verified: 229/229 archived plans vectorized, 1112
|
||
chunks, formerly-lost plans answering semantic queries at 85%+ similarity.
|
||
990 memory tests green.
|
||
|
||
- **CLOSED_PLANS ledger append race (@flow): concurrent plan closes lost
|
||
entries.** `append_to_closed_plans` was an unlocked read-modify-write; the
|
||
S314 bulk sweep lost 18 of 21 entries to it (reconciled by hand). Now guarded
|
||
by an `O_CREAT|O_EXCL` lockfile with retry/backoff, and the previously
|
||
silent append failure is surfaced in close output and logs. 730 flow tests
|
||
green.
|
||
|
||
- **Telegram routine read-timeouts no longer logged as errors (@skills,
|
||
Patrick ruling): ends the medic wake-loop.** A routine long-poll read
|
||
timeout (`socket.timeout` — an `OSError` subclass) slipped past the earlier
|
||
`URLError`-only guard into the network-outage path, logging ERROR once per
|
||
episode (~576 lines/30h) and waking @trigger's medic each time. The
|
||
`_is_routine_read_timeout` guard now covers the `OSError` handler too, and
|
||
the genuine-outage episode-start line is demoted ERROR→WARNING (backoff
|
||
self-heals; recovery already logs INFO; medic only fires on ERROR/CRITICAL).
|
||
Real failures still log ERROR. 825 telegram tests green.
|
||
|
||
### Security
|
||
|
||
- **Hook config trust model hardening (DPLAN-0244): closes a zero-interaction
|
||
RCE from untrusted `.aipass/hooks.json`.** The hook loader walked up from CWD
|
||
and trusted any `.aipass/hooks.json` it found; since the bridge is wired
|
||
globally in provider settings, a hostile repo shipping a `command`-type hook
|
||
could execute arbitrary shell on `SessionStart` with no user interaction.
|
||
Fixed with defense-in-depth. **Layer A (engine):** per-project configs may no
|
||
longer run `command`-type hooks (refused via an unconditionally-stamped
|
||
`_source` provenance flag), and handler paths are gated to the `aipass.*`
|
||
namespace. **Layer B (loader + CLI):** a trusted-project registry
|
||
(`~/.aipass/trusted_projects.json`, path + sha256) that the loader checks
|
||
fail-closed; on upgrade it bootstraps **only** the `$AIPASS_HOME` install
|
||
(never trust-on-first-use of an arbitrary directory); `aipass init`/`init
|
||
update` auto-enroll, and new `aipass trust`/`revoke` commands manage
|
||
enrollment. Both gates proven to block the attack independently via a live
|
||
acceptance test driving the real bridge with a real payload. 1105 hooks +
|
||
133 aipass tests green. Origin: external scan (false positive at
|
||
`engine.py:37`) whose triage surfaced the real adjacent hole.
|
||
|
||
### Added
|
||
|
||
- **Supply-chain hardening pass (DPLAN-0243): commit signing + hash-pinned CI
|
||
tooling + release provenance.** All commits are now SSH-signed via a
|
||
dedicated repo-scoped signing key (first signed commit 9048666c, verified
|
||
`Good "git" signature`). Every standalone pip tool install across the four
|
||
CI workflows now installs `--require-hashes` from lock files in
|
||
`.github/requirements/` (pip/ruff/build/pytest/pip-audit), generated with
|
||
full multi-platform hash coverage — including the Windows `colorama` marker
|
||
dependency that naive Linux-side pinning silently drops. `publish.yml`
|
||
gained a SHA-pinned build-provenance attestation step (activates on the
|
||
next release), and Dependabot now watches the new lock directory as a
|
||
grouped `pip` ecosystem. Editable `-e .` installs untouched. Full 31-check
|
||
CI matrix green on the change. Driven by the OpenSSF Scorecard gaps
|
||
surfaced via hvtracker.net (HVTrust 82.0, #1 in Multi-Agent Systems);
|
||
detector-gap correction filed upstream as YugantM/hvtracker#186 (Claude
|
||
Code-native projects misread as "no Anthropic dependency").
|
||
|
||
### Fixed
|
||
|
||
- **CI green pass on the runaway-log PR — every red was ours, every fix
|
||
verified.** Morning-after triage of PR#696's failing checks: the test
|
||
matrices' only failure was the known parked flake, but lint and the seedgo
|
||
audit were genuinely red from the previous night's new code. One `ruff
|
||
format` on trigger's runaway-handler tests fixed lint. The audit findings
|
||
went back to their owners by dispatch: @hooks built the branch's missing
|
||
json_handler and wired it into `persistent_alert`/`alert_dismiss`, added the
|
||
introspection no-args gate, and flattened `_menu_live()`'s nesting
|
||
(1071 tests green); @prax refactored `rate_tracker.py` to dependency
|
||
injection — the module layer now injects `logs_dir` and `trigger.fire` via
|
||
`configure()`, so the handler carries no cross-handler or handler→module
|
||
imports (1028 tests green). Both branches re-audit at 100% across all 41
|
||
standards, independently verified. Detection re-proven live post-refactor
|
||
with a fresh planted log storm. Also trimmed the tier-1 navmap prompt
|
||
(9.3k → 7.9k chars, under its ~8k injection cap) with the comms doctrine
|
||
intact.
|
||
|
||
- **Windows flake pinned: `test_is_pid_alive_dead` escaped the ca096295
|
||
sweep.** That commit's rule — tests mocking `os.kill` must pin
|
||
`sys.platform="linux"` because Windows takes the ctypes OpenProcess path and
|
||
never reaches the mock — was applied to every pid-liveness test except this
|
||
one. It only failed when PID 1234 happened to be alive on the runner
|
||
(environment lottery, first hit today). Pinned like its siblings. The
|
||
remaining Windows session_boot reds and the relay mtime-cache flake predate
|
||
this PR and stay parked.
|
||
|
||
- **The parked reds, unparked — Patrick's ruling: red CI is never parked.**
|
||
"If CI is red, it's because you or I left it red." Both remaining reds fixed
|
||
by their owners the same hour. @prax root-caused the relay mtime-cache flake:
|
||
the test only passed when two writes landed in the same mtime-granularity
|
||
window (true locally, false on CI runners) — fixed by pinning mtime with
|
||
`os.utime` so the cache contract is tested deterministically, proven 50/50 +
|
||
20/20 loops. @hooks root-caused the four Windows session_boot reds: the boot
|
||
path's `_tmux_session_exists()` ran a real `subprocess.run(["tmux", ...])`
|
||
that Windows runners can't satisfy (WinError 2) — mocked in all four tests
|
||
per the ca096295 convention, leaving `execvp` (already mocked) as the only
|
||
terminal call. Ruling recorded in compass; the "forget CI" era is over.
|
||
|
||
- **Burst-evasion closed: bursty runaways can no longer slip past the rate
|
||
tracker.** Found live during the morning's chain verification: any single
|
||
below-threshold 10-second scan window zero-reset the sustain counter, so a
|
||
bursty writer (20 short lines every 6 seconds — 200 lines/min average, the
|
||
exact retry-loop-with-sleep shape of the TG relay incident) ran 4 minutes
|
||
undetected. @prax's fix (rate_tracker v1.2.0): severity now evaluates
|
||
`max(instant_rate, 60s window average)` — continuous writers behave exactly
|
||
as before (instant rate dominates), bursts sustain through their gap
|
||
windows, and subsidence still clears as zeros fill the window. Four new
|
||
burst tests; live-proven with the previously-evading storm pattern:
|
||
`RUNAWAY WARNING: prax_burst_storm_test.log — 191 lines/min sustained 120s`
|
||
in the tracker log, fired from the restarted running service. Detection
|
||
evidence now spans all three storm shapes: continuous fast (332/min),
|
||
continuous moderate (257/min), bursty (191/min).
|
||
|
||
### Added
|
||
|
||
- **TG streaming v2 polish (DPLAN-0229): the last two finalize paths now
|
||
honor the streaming flag.** v1 shipped with a deliberate gap — when logs
|
||
were active mid-turn or the final response exceeded 4096 chars, the Stop
|
||
hook fell back to "Done." + a fresh message, orphaning the streamed bubble.
|
||
@hooks threaded `streaming` through `_deliver_chunks`: logs-active now
|
||
reconcile-edits the streamed message with the final formatted response, and
|
||
multi-chunk edits chunk 1 in place then sends [2/N]+ as continuations.
|
||
Batch mode is verified zero-change (regression tests for both paths), plus
|
||
an edit-fail fallback. 6 new tests, 1077 green. Live streamed-turn proof
|
||
pending Patrick's next streaming session — honestly flagged, not faked.
|
||
|
||
## [2026-07-14]
|
||
|
||
### Fixed
|
||
|
||
- **Prax TG relay gets the same offline backoff as the bots.** Found in
|
||
Patrick's live plug-pull test: the bots went quiet correctly, but the
|
||
monitor→Telegram relay kept logging `Send failed` every ~5 seconds (89 lines,
|
||
no backoff) — and each failed-send error was re-ingested by the log watcher,
|
||
feeding the relay more events to fail on. Now network-class send failures put
|
||
the relay in offline mode: doubling backoff (1s→60s cap), flush gate skips
|
||
sends while offline so the monitor loop never blocks and viewers keep
|
||
rendering, log-once semantics (one enter line, one 5-minute summary, one
|
||
recovery line with drop count), full reset on first successful send. 11 new
|
||
tests; 1007 prax green.
|
||
|
||
- **TG bots no longer hot-spin when the internet drops.** Live find from
|
||
Patrick's on-location tether outage: DNS failure makes `urlopen` fail
|
||
instantly (no 30s long-poll wait), so the shared poll loop retried as fast as
|
||
it could — up to 13 ERROR lines/second per bot, all 5 bots spinning for the
|
||
whole offline window (rotation saved the disk; nothing saved the CPU, and the
|
||
flood tripped the medic circuit breaker fleet-wide). Now network-class poll
|
||
failures (DNS/connection/socket, classified via `_NetworkPollError`) back off
|
||
exponentially 1s→60s cap and reset on the first successful poll, with
|
||
log-once semantics: one "unreachable, backing off" line, one summary per 5
|
||
minutes while offline, one recovery line with suppressed count. Routine
|
||
long-poll read-timeouts (expected getUpdates behavior, ~863 medic-suppressed
|
||
events/day) no longer log at all. Bots still self-recover the moment
|
||
connectivity returns. 25 new tests; 822 TG + 252 skills green.
|
||
|
||
### Added
|
||
|
||
- **Runaway-log detection + escalation — designed and built by the agents
|
||
themselves.** Patrick's mission brief went to @prax as lead ("I don't want it
|
||
to be you" — devpulse relayed requirements, not a design): prax researched,
|
||
collaborated with @hooks and @trigger by mail, wrote DPLAN-0242, and ran the
|
||
build as TDPLAN-0013 across three branches. The system: @prax `rate_tracker`
|
||
watches every log in `system_logs/` for volume (not content — orthogonal to
|
||
medic), disk-persisted state, WARNING at >100 lines/min sustained 2 min /
|
||
CRITICAL at >10 lines/sec 1 min, per-file suppression, runs as a 4th monitor
|
||
thread, plus `drone @prax log-health` for an at-a-glance rate overview.
|
||
@trigger registers the new `runaway_log_detected` event and dispatches to the
|
||
responsible branch with a per-file 30-min cooldown deliberately independent
|
||
of medic's circuit breaker (a storm can't silence both systems), UNKNOWN
|
||
attribution falls back to @prax, and every alert is written to
|
||
`.aipass/alerts.json`. @hooks `persistent_alert` injects an advisory banner
|
||
into every agent's prompt until the alert is fixed or dismissed
|
||
(`drone @hooks dismiss <id>`) — general-purpose, any agent can raise alerts.
|
||
Devpulse verification found and fixed the last-mile gaps: both hooks pieces
|
||
stopped at the first `.aipass/` dir walking up (every branch has one — the
|
||
banner could never render), and hooks.json registration alone isn't
|
||
deployment — the handler needed manual wiring into `~/.claude/settings.json`
|
||
(agents can't edit it; documented for future handlers). Live-fire acceptance:
|
||
a planted 240 lines/min storm was detected at 257 lines/min sustained 120s →
|
||
event → dispatch → **@aipass woke autonomously, root-caused the test writer
|
||
down to its PID and loop shape, triaged no-action** → alerts.json → banner
|
||
renders → dismiss clears. ~77 new tests across four branches, suites green
|
||
(prax 1028, trigger 619, hooks 1071), seedgo 98–100%.
|
||
|
||
- **Citizens wake each other freely — wake-back for everyone, devpulse
|
||
unwakeable by design.** Patrick's ruling after two team-mission stalls in one
|
||
evening (prax emailed sleeping collaborators; trigger replied instead of
|
||
dispatching back — replies never wake, and wake-back was owner-gated so
|
||
agent-to-agent dispatch never woke the sender). @ai_mail removed the
|
||
owner-gate: any citizen sender is woken when its dispatched agent completes
|
||
(proven live: "@trigger woken after @aipass completed" — first citizen
|
||
wake-back ever). @devpulse is now structurally unwakeable via a
|
||
`citizen_class: manager` check on every wake path — mail always lands, wake
|
||
always skips, no longer dependent on an interactive session happening to be
|
||
open. The gate removal exposed a self-wake loop within minutes (wake-back
|
||
sessions were attributed to @ai_mail as sender, so ai_mail kept waking
|
||
itself; the depth cap stopped it after one cycle) — fixed the same night:
|
||
self-wake guard + wake-back sessions carry no sender, so chains terminate at
|
||
the original dispatcher. 765 ai_mail tests green. The navmap gained a
|
||
"Talking to other agents" section: dispatch vs email semantics, team-relay
|
||
discipline, and the manager exception.
|
||
|
||
- **Medic is back on — and the loop is proven live.** Off since 2026-05-10 (a
|
||
pytest fixture storm flooded the error registry; the off switch was pulled to
|
||
stop the noise and forgotten for 65 days). Three fixes made re-enable safe:
|
||
(1) @prax: pytest logging routes to a temp dir when `PYTEST_CURRENT_TEST` is
|
||
set — test fixtures can never pollute production `logs/` again (the storm
|
||
class that caused the shutdown); (2) @trigger: circuit breaker self-heals —
|
||
open breakers half-open on read, close on a successful probe, cooldown decays
|
||
to base (previously `half_open` was a terminal trap and only manual reset
|
||
recovered); (3) @trigger: **TTL mutes** — `medic mute @branch` and `medic off`
|
||
now auto-expire after 24h by default (`--for 48h/7d` custom, `--forever`
|
||
explicit kill switch; temp `off` keeps detection running). Agents doing build
|
||
work mute themselves and never have to remember to unmute — the permanent
|
||
switch that got medic forgotten no longer exists. Breadcrumbs shipped: ai_mail
|
||
footer + navmap tell every agent to mute before build work. Live-fire proof:
|
||
a planted commons SQL bug was detected, dispatched, and fixed byte-identical
|
||
by @commons in 105 seconds (15/15 tests green); a real TG poll error was
|
||
correctly triaged NOT ACTIONABLE; organic instance-lock noise was correctly
|
||
triaged LOW/expected. @skills/@api on 7-day mutes until the TG poll-level fix
|
||
lands. 993 prax + 603 trigger tests green.
|
||
|
||
- **Prax monitor: concurrent viewers — laptop and Telegram mirror side by
|
||
side.** Patrick's ruling after being locked out of his own monitor three
|
||
times: *processes are not agents; display processes must never be
|
||
single-instance.* The instance lock is gone from the display path — any
|
||
number of `monitor run` viewers start and render concurrently. The lock is
|
||
scoped to the one true single-writer responsibility: the Telegram relay
|
||
(`relay.pid`, held by `prax-monitor.service`); extra instances run
|
||
viewer-only, so no TG double-sends. The misleading "kill the existing
|
||
process" error is dead. 998 prax tests green, 3 new concurrent-viewer tests;
|
||
live-verified: interactive Mission Control rendering while the TG relay
|
||
service runs untouched.
|
||
|
||
- **Telegram user-comment mirror: the TG chat now shows the whole conversation,
|
||
whichever door you speak through.** Patrick's spec from the live cross-door
|
||
drill: his own messages typed in the terminal or claude.ai remote never
|
||
appeared in TG — only the replies did. New `user_message_relay` UserPromptSubmit
|
||
handler (@skills-built, self-contained in the telegram skill, registered by
|
||
@hooks as the last, crash-isolated entry) posts genuine user messages to the
|
||
branch's TG chat with an origin tag, silently (`disable_notification`). Noise
|
||
fences keep it human-only: system/task notifications, slash-command output,
|
||
dispatch wake prompts, sub-agent prompts, TG-origin echoes, and consecutive
|
||
dupes are all skipped (structural session-type detection was investigated and
|
||
rejected — it's session-wide, would eat genuine mid-flight messages). Inbound
|
||
hardening rides along: stale pending files cleaned before each write, and an
|
||
undelivered-response overwrite now logs a warning instead of silently losing
|
||
the reply. 47 new TG tests; registration execution-proven via engine.jsonl and
|
||
the positive path live-verified — a terminal-door message delivered to the
|
||
real TG chat. TG dormancy/proactive push deliberately untouched (design chat
|
||
with Patrick pending).
|
||
|
||
### Fixed
|
||
|
||
- **TG bot heartbeat race: delivered replies no longer flip back to
|
||
"Processing…".** Patrick watched his answered bubble get overwritten live: a
|
||
heartbeat thread stuck >5s in a slow Telegram edit call survived its stop
|
||
(the join timed out), woke to a *shared* stop Event the next message had
|
||
already cleared, and re-edited the old placeholder with "Processing…
|
||
(elapsed)" over the delivered reply. Fixed structurally (@skills, devpulse
|
||
root-cause brief): a generation counter captured per heartbeat thread —
|
||
any stale thread breaks before every edit — plus a delivered re-check
|
||
immediately before each edit call in both batch and streaming loops.
|
||
Second bug in the same window: rapid-fire messages (photo + text in one
|
||
turn) overwrite the bot's single pending slot, stranding the earlier
|
||
placeholder frozen; superseded placeholders are now finalized to
|
||
"⏭ Superseded by newer message" in both message and file paths. 6 new
|
||
heartbeat tests; full TG suite 797 green (devpulse-verified). Deployment
|
||
lesson from the same morning: bot fixes aren't live until the systemd
|
||
units restart — commit ≠ deploy.
|
||
|
||
- **TG mirror live-test fixes: main-chat messages mirror, TG messages don't
|
||
echo.** Patrick's first morning test caught what 47 green tests missed: the
|
||
relay's sub-agent skip blocked ALL daemon-backed main chats (they run with
|
||
`--agent claude`, so `agent_type="claude"` — and real sub-agents never fire
|
||
UserPromptSubmit at all; the filter's premise was empirically wrong across the
|
||
entire engine log). Skip is now agent_id-based (defensive, never observed).
|
||
Second catch from tracing his test: TG messages inject into tmux as raw text —
|
||
no `via Telegram:` marker — so the TG-origin filter never matched and every
|
||
TG message would have echoed back once the first fix landed. New structural
|
||
gate: the bot stores the injected prompt in its pending file; the relay skips
|
||
a prompt that text-matches a fresh undelivered pending entry. Mirror proven
|
||
live by Patrick across both directions ("success :)"). 791 TG tests green.
|
||
|
||
- **DPLAN-0241 round 4 (night shift): user flags survive every launch path, and
|
||
every session is born with an honest name.** R6 — the bug behind Patrick's
|
||
approve-everything chat: the boot menu suppressed its bypass defaults when the
|
||
user passed `--permission-mode` himself, but only the fresh-launch path threaded
|
||
the user's flags into the exec — resume, takeover, continue, and dead-window
|
||
paths all launched flagless. `extra_args` now threads through ALL launch paths
|
||
(headless `-p` included). R7 — auto-namer: every launch is stamped
|
||
`--name <branch>-<short-session-id>` (flag live-verified on claude 2.1.209; a
|
||
user-passed `-n/--name` wins), so made-up auto-names can no longer hide which
|
||
chat is which. Plus four drill nits: new-over-all ABORTS if the daemon stop
|
||
fails (one brain even in failure paths), close-all's failure hint no longer
|
||
recommends the mechanism that just failed, `exit`/`q`/`quit` quietly leave every
|
||
menu, session rows stay rich (PID, kind, name, age). Surgical-stop probe:
|
||
`op:kill` exists in the daemon's Unix-socket control protocol (per-job bg stop,
|
||
8-char sessionId prefix, no auth) — documented in DPLAN-0241, deliberately NOT
|
||
shipped: undocumented internal protocol. 1048 hooks tests green (102
|
||
session_boot, 11 real-binary CLI contract).
|
||
|
||
## [2026-07-13]
|
||
|
||
### Fixed
|
||
|
||
- **DPLAN-0241 rounds 2-3: Enter IS the takeover — background chats reopen as
|
||
normal terminal chats.** Live incident round two (Patrick's laptop, 23:00): the
|
||
boot menu's resume for a background chat opened the `claude agents` viewer, which
|
||
dispatched his typed message as a brand-new bg job WITHOUT bypass permissions —
|
||
and the shipped stop path called `claude agents stop`, a subcommand that does not
|
||
exist (987 mocked tests never noticed). All fixed by @hooks across two rounds,
|
||
every CLI fact live-verified against claude 2.1.208: phantom stop removed
|
||
(bg close is now honest — no per-job stop exists in the CLI; SIGTERM never used
|
||
on bg, the daemon respawns it); Enter on a live bg session now takes the chat
|
||
over — `claude daemon stop --any` (returncode-checked, blast-radius listing +
|
||
y/N confirm when other branches' bg sessions would also stop) then `--resume
|
||
<sessionId>` inside tmux with bypass; ALL interactive launches tmux-wrapped so a
|
||
closed terminal is always recoverable; multi-session menu shows real session
|
||
names, requires an explicit pick, and its new/close paths stop-first honestly;
|
||
new real-binary CLI contract test tier (20 tests probing every claude
|
||
flag/subcommand our code invokes — the phantom-subcommand class is now
|
||
structurally unshippable). 1025 hooks tests green. North-star architecture
|
||
recorded from Patrick's rulings: one conversation per branch; TG/claude.ai/
|
||
terminal are views of it; agents bind to the machine, not the interface.
|
||
|
||
- **Session management overhaul (DPLAN-0241): one brain per branch, attach-first
|
||
boot menu, honest session listings.** Born from a live incident — Patrick locked
|
||
out of a running chat for an hour. Root causes, all fixed by @hooks: the bashrc
|
||
boot shim hijacked EVERY `claude` invocation (so `claude agents`, the real
|
||
attach path, never executed) — now intercepts only bare/`--permission-mode`
|
||
launches; session_boot printed one PID from a list and advised `kill` for
|
||
daemon-managed background sessions (which respawn — the unwinnable loop) — now
|
||
a 3-option boot menu (resume / start-new-closes-old / close) with per-kind
|
||
proper stops; presence_gate (single-session enforcement) had NEVER run in
|
||
production (`provider_wired: false`, absent from settings.json, zero engine
|
||
entries ever) and carried two latent bugs (self-PID resolver matched
|
||
comm=="claude" but CC binaries are version-named; agent_type skip waved through
|
||
daemon bg sessions) — both fixed, wired, shipped OBSERVE-ONLY for a soak period
|
||
per prior-art recall (the gate false-blocked a real resume in the
|
||
PRESENCE-file era); wire_verify no longer excludes unwired security hooks from
|
||
its check (enabled-but-unwired = ERROR); new `drone @hooks sessions` +
|
||
`sessions reclaim` one-command reset; session listings/names standardized to
|
||
`PID · branch · short-id · kind · age`. Verified live: gate's first production
|
||
run correctly logged a would-block for a real duplicate session without
|
||
self-blocking. 987 hooks tests green (26 new/updated).
|
||
|
||
## [2026-07-12]
|
||
|
||
### Added
|
||
|
||
- **Telegram log-stream control: `/logs` on branch bots + interactive Prax
|
||
Monitor chat.** The per-branch session LogStreamer auto-started on first
|
||
message hardwired to full firehose with no off switch; the Prax Monitor
|
||
relay chat was send-only — no command menu, and anything typed there was
|
||
silently never read (nothing polled that token). @skills added `/logs
|
||
on|errors|off|status` to all branch bots (preference persisted per chat,
|
||
honored by the auto-start; 33 tests) and a new `PraxMonitorBot` receiver
|
||
service (`telegram-bot@prax_monitor`) with `/pause /resume /errors /all
|
||
/status` and a registered command menu (34 tests). @prax made the relay
|
||
honor the shared control file (`~/.aipass/telegram_bots/
|
||
prax_monitor_control.json`, frozen contract: paused + level) each 5s flush —
|
||
paused discards, `errors` filters to WARNING/ERROR/CRITICAL (17 tests).
|
||
Live-verified end-to-end from Telegram Web: `/errors` silenced INFO batches
|
||
within one flush, `/all` restored them.
|
||
|
||
### Fixed
|
||
|
||
- **Legacy `builder` citizen_class migration + birth-certificate template
|
||
(fixes #692).** `builder` was renamed to `aipass_framework` on 2026-07-01
|
||
(13463c0c) as a pure rename, but passports minted pre-rename kept the retired
|
||
name, and the seedgo Architecture checker requires
|
||
`spawn/templates/<citizen_class>/` — hard-capping those citizens below 100%
|
||
(Vera Studio's @vera/@writer stuck at 99%; same legacy class found in 6
|
||
external projects). @spawn completed the rename instead of resurrecting a
|
||
`builder` template: `sync-registry --fix` now migrates the exact value
|
||
`builder` → `aipass_framework` in passports (idempotent, dry-run safe, 3 new
|
||
tests), so external projects self-heal via `aipass doctor --fix`. Also fixed
|
||
the template leftover that kept minting the retired name:
|
||
`birth_certificate.json` now renders `{{CITIZEN_CLASS}}` like the passport
|
||
does. Verified: dry-run against Vera Studio's live registry plans exactly the
|
||
two migrations with zero writes; spawn 347 tests green. #695 closed won't-fix
|
||
(armed Monitor-tool watchdog is the dispatch indicator; always-arm is the
|
||
rule).
|
||
- **Order-dependent `test_missing_file` + skills test litter (fixes #694).**
|
||
Root cause was @prax's `json_handler_module` fixture popping EVERY branch's
|
||
json_handler from `sys.modules` (never restored), orphaning the module object
|
||
@skills' conftest had patched — `test_missing_file` then re-imported a fresh
|
||
module pointed at the real `skills_json/`, planted `ghost_config.json`, and
|
||
failed on it every later full-repo run (the only failure in an 11k-test
|
||
sweep). @prax scoped the eviction to `aipass.prax.*` via
|
||
`monkeypatch.delitem` (auto-restore). @skills made all 4 resilience tests
|
||
hermetic (patch `SKILLS_JSON_DIR` → `tmp_path` inside the test body, immune
|
||
to sys.modules state), fully-qualified the legacy bare `skills.`
|
||
`BRANCH_MODULE` in 3 test files (the source of the remaining litter), and
|
||
fixed a latent wrong-variable assert. Verified: original failing pair now
|
||
passes both orders, prax+skills+spawn 1576 tests green, `skills_json/` stays
|
||
clean after a full run.
|
||
|
||
## [2026-07-11]
|
||
|
||
### Added
|
||
|
||
- **Owner seating made permanent + self-healing for every project (DPLAN-0239,
|
||
fixes #693).** The owner-capability guard was correct but the DATA was never
|
||
seeded: every project created before 2026-07-10 had its owner only in the
|
||
self-editable passport, never in the sealed registry (8/8 external projects
|
||
unseated; AIPass's own registry was missing `metadata.id` with 13 entries
|
||
sharing one stale id). Identity model settled: registry `metadata.id` =
|
||
project credential (passports conform); branch-entry `registry_id` =
|
||
set-once PER-CITIZEN UUID minted at entry creation; entry `owner:true` =
|
||
the authority gate (first agent), chosen by ONE shared heuristic
|
||
(`pick_owner_branch`: manager → passport owner → first-created).
|
||
New: `drone @spawn sync-registry --check [--json]` (read-only, 7 health
|
||
flags, pinned JSON schema) and `--fix [--dry-run]` (idempotent reconcile:
|
||
seat owner, majority-consensus restore of `metadata.id`, mint citizen UIDs,
|
||
align passports; dry-run fully read-only; never moves a seated owner).
|
||
`aipass doctor` renders owner health per flag; `doctor --fix`, `install`,
|
||
and `init update` delegate repair to spawn — existing/external projects
|
||
self-heal on next update (the missing DPLAN-0231 PART-4 trigger). The adopt
|
||
path now seats owners; `placeholders.py` resolves the registry from the
|
||
target dir (was CWD) and fails loud. @hooks `auto_watchdog` now injects the
|
||
real Monitor-tool watchdog command with the actual @target (was a dead
|
||
one-liner + `run_in_background`, which cannot wake a session). Deployed
|
||
live: AIPass + 6 external projects reconciled and verified clean — VERA is
|
||
now seated owner of Vera Studio (`is_owner('@vera') = True`, was refused).
|
||
Owners built (spawn 343 / aipass 673 / hooks 961 tests green); devpulse
|
||
verified every diff, live-ran every stage, full-repo sweep 9364 passed
|
||
(1 pre-existing skills litter fail → #694).
|
||
|
||
### Changed
|
||
|
||
- **Fleet seedgo compliance sweep — every branch to 100% (issues #686, #661).**
|
||
Overnight campaign bringing all branches to 100% on the seedgo standard pack.
|
||
#686 (Subcommand_Help, per the #685 contract): entry points intercept
|
||
`<cmd> --help` before dispatch, so `--help` shows help instead of executing.
|
||
#661 (Output_Routing): status/error console output routed through the shared
|
||
`@cli` `success()/error()/warning()` helpers instead of raw `console.print`
|
||
markup. Owners self-audited and self-fixed their own branches; devpulse verified
|
||
each diff + re-ran each audit and committed per wave. Landed so far: spawn,
|
||
drone, flow, daemon, prax, ai_mail, backup, seedgo, memory, trigger, api, cli,
|
||
aipass, commons — all 14 offenders now at 100%. **Fleet: 17/17 branches at
|
||
100% seedgo compliance** (hooks, skills, devpulse were already compliant).
|
||
Owners self-audited and self-fixed; devpulse verified every diff, re-ran each
|
||
branch's full test suite, and committed per wave. A full 17-branch test run
|
||
(~10,349 tests) surfaced one pre-existing flaky test in drone
|
||
(`test_pr_no_branch_dir` / `test_pr_no_args` lacked cwd isolation, so a real
|
||
checkout's findable passport made the auth path pass unexpectedly) — given
|
||
`monkeypatch.chdir(tmp_path)` isolation to match its sibling test, so the full
|
||
suite is now deterministically green.
|
||
|
||
### Fixed
|
||
|
||
- **Watchdog Monitor wake no longer double-fires (#693 follow-on, reported by
|
||
VERA via the feedback channel).** The `watchdog agent` reminder banner
|
||
("invoke via Monitor tool, not run_in_background") printed to STDOUT at arm
|
||
time, and the harness Monitor tool treats every stdout line as a wake event —
|
||
so every armed watchdog fired a spurious wake the instant it armed, then the
|
||
real wake at completion. Rerouted to stderr (`err_console`); stdout now
|
||
carries completion/stall events only, matching the contract the agent handler
|
||
already followed. Verified live: exactly one wake, on real exit. The devpulse
|
||
README watchdog/feedback sections were also rewritten to document the owner
|
||
gate, the 3-step Monitor wake mechanic, why no passive wake can exist, and
|
||
the 600 s default timeout.
|
||
|
||
- **Watchdog agent tests thread-race flakes made deterministic (devpulse).**
|
||
Four tests patched the GLOBAL `time.sleep` with stateful/side-effecting
|
||
fakes; prax's logger spawns daemon threads on first log, which executed the
|
||
fakes concurrently with the test (advancing a fake clock, unlinking the
|
||
fixture lock early, or re-truncating `last_bounce.json` mid-read in
|
||
`_classify_exit` → `exit_code=None`). All fakes are now thread-scoped via a
|
||
caller-frame guard: only sleeps from the agent module trigger the test's
|
||
side effect; foreign threads get a real 1 ms sleep.
|
||
|
||
- **seedgo-audit back to 100 % after the S300 commits (PR659).** Two 99 %
|
||
regressions from that day's own work: `aipass` `doctor.py` `_fix_owner_seating`
|
||
had two silent catches (now log via prax like the sibling check function),
|
||
and the devpulse README claimed 407 tests where the readme checker counts
|
||
test functions (corrected to 309).
|
||
|
||
- **Two more non-hermetic ai_mail tests made deterministic (PR659).** With the full
|
||
suite now running on varied CI runners, `test_get_pid_cwd_darwin_failure` and
|
||
`test_is_zombie_linux_no_proc` intermittently failed: they called the real `lsof`
|
||
(via `subprocess.run`) and real `open("/proc/…")` for a fixed PID (999 / 99999),
|
||
so on a runner where that PID happened to exist they returned a non-`None` result
|
||
instead of the expected failure. Mocked `subprocess.run` and `builtins.open` so the
|
||
tests assert the failure contract without touching real process/`/proc` state.
|
||
Test-only; deterministic across repeated runs.
|
||
|
||
- **Windows CI cross-platform fixes — `windows-setup` green (PR659).** Fixing the
|
||
telegram collection errors unmasked 14 pre-existing Windows-only failures across
|
||
six branches. Two root causes. **(1) pid-liveness tests** (ai_mail, flow, hooks,
|
||
skills) mocked `os.kill`, but the production `_is_pid_alive` already branches to a
|
||
ctypes `OpenProcess` path on Windows and never reaches `os.kill`, so the mocks had
|
||
no effect and the real path ran instead — pinned `sys.platform` to `linux` in those
|
||
tests (or patched `_is_pid_alive` directly) so they exercise the POSIX contract
|
||
deterministically on every platform. **(2) POSIX path assumptions** — prax's jsonl
|
||
test hardcoded `/some/path` (backslashes under `str(Path)` on Windows) now asserts
|
||
against `str(test_path)`; hooks' rollover test compares `repr()` (matches `%r`
|
||
logging); ai_mail's darwin lsof-parser test uses a fixed POSIX path; and seedgo's
|
||
`is_bypassed()` now normalizes the rule file via `Path(rule_file).as_posix()` before
|
||
matching (the one production fix — Windows backslash rule paths never matched the
|
||
forward-slash file path). 10 files (9 test, 1 code); owners self-fixed, devpulse
|
||
verified every diff + Linux no-regression (525 changed-test assertions green).
|
||
|
||
- **Flaky `test_deletes_old_system_log` made deterministic (@prax log-sweep tests).**
|
||
The sweep integration test reached `log_watchdog._get_system_logs_dir` through a
|
||
`_get_sweep()` wrapper and patched it by string path; a sibling test
|
||
(`test_logging_handlers.py`) `sys.modules.pop`s and reimports `log_watchdog`,
|
||
creating a second module object — so the string patch could target a different
|
||
object than the function's `__globals__`, the sweep scanned the real (empty)
|
||
`system_logs/`, removed 0 files, and `assert files_removed == 1` failed
|
||
intermittently (the same commit passed in one CI run and failed in another).
|
||
Switched to a direct `import log_watchdog as lw` + `patch.object(lw, …)` (shared
|
||
module `__dict__`) and patched `json_handler` to block real file I/O. Test-only
|
||
(1 file); prax suite 978 green, two full-repo runs 11,019 passed each, sweep tests
|
||
deterministic across repeated runs.
|
||
|
||
- **Telegram skill tests made CI-safe — full-repo collection + hermeticity (issue #691).**
|
||
The 16 test files under `skills/lib/telegram/tests/` imported handlers via bare
|
||
`from apps.handlers…`, which collided with other branches' `apps` packages during
|
||
full-repo CI collection (~16 ImportError collection errors → CI red on Linux +
|
||
Windows). Converted to fully-qualified `aipass.skills.lib.telegram.apps.handlers.*`
|
||
imports (and matching `mock.patch` targets). Verifying that fix surfaced a second
|
||
problem the imports had exposed: ~11 tests reached the live Telegram API
|
||
(`base_bot.run → _set_command_menu → set_bot_commands → urlopen`) — they had never
|
||
run in CI before because they failed at collection. Added a session-scoped autouse
|
||
`_block_network` conftest fixture that patches `urlopen` on the four network-using
|
||
telegram modules (both bare and fully-qualified import paths, each guarded) so any
|
||
test attempting a live HTTP call fails loud instead of hanging. A full-repo CI run
|
||
then exposed a third layer the isolated suites had hidden: `handler.py` and its
|
||
routing tests still used bare `from apps.handlers.X import Y` / `mock.patch("apps.
|
||
handlers.X…")`, which resolve to the *wrong* branch's `apps` in a whole-repo run
|
||
(AttributeError / ModuleNotFoundError at runtime — 17 `test_handler_routing`
|
||
failures). Fully-qualified those to `aipass.skills.lib.telegram.apps.handlers.*` in
|
||
both `handler.py` (7 lazy imports, now house-rule compliant) and the tests; the
|
||
skill's runtime behaviour is unchanged (verified via `drone @skills run telegram`).
|
||
Net: full-repo collection 0 errors and the whole 11k-test suite green; telegram
|
||
suite 663 passed / 0 failed / 0 hangs, fully hermetic; coverage intact (import and
|
||
patch-target rewiring only — zero assertion changes).
|
||
|
||
- **`aipass install` from a throwaway path can no longer hijack the machine-wide
|
||
`AIPASS_HOME` (issue #688).** A probe install run from a `/tmp` scratchpad had
|
||
rewritten `~/.claude/settings.json` `env.AIPASS_HOME`, silently pointing every
|
||
Claude Code session on the machine at a dead temp tree (stale python, stale
|
||
hooks — surfaced as bogus ImportErrors in unrelated work). Three defenses:
|
||
`bootstrap.is_throwaway_path()` gates the settings write itself (temp dirs +
|
||
scratchpads never land in global settings); `run_install` refuses a throwaway
|
||
home loudly with `--force-global-home` as the explicit override; and
|
||
`aipass doctor` gains a `global AIPASS_HOME` check that flags a nonexistent or
|
||
throwaway path with fix guidance. +11 tests. Ships with a probe-hygiene SOP
|
||
(`aipass/docs/probe_hygiene.md`): temp installs are used, deleted, gone — nothing
|
||
permanent may point at a temp path. Tests made location-independent so the suite
|
||
is green from any cwd and from a `/tmp` clean-room extraction, not just the repo
|
||
root. (built by @aipass, verified + test-hardened by devpulse against the real
|
||
hijack path)
|
||
|
||
## [2026-07-10]
|
||
|
||
### Added
|
||
|
||
- **Owner-capability model — project ownership sealed in the registry, and the
|
||
owner is woken back when a dispatched agent completes (issue #678).** The
|
||
directed-wake round-trip grew into an access-control primitive: watchdog /
|
||
feedback / wake-back are owner-only privileges, and the owner (first agent /
|
||
`citizen_class: manager` — devpulse in AIPass) is resolved from the *sealed*
|
||
`*_REGISTRY.json`, not the self-editable passport (no self-grant). Three parts
|
||
built in parallel against a frozen `is_owner` contract: `@spawn` writes
|
||
`owner` + `registry_id` into registry entries and exposes `get_owner()` /
|
||
`is_owner()` (`ensure_project_has_owner` now keys off the manager signal, not
|
||
the created-date heuristic that mislabeled `@aipass`); `@hooks` adds a
|
||
`registry_gate` PreToolUse handler that blocks raw writes/edits/deletes of
|
||
`*_REGISTRY.json` and redirects to `drone @spawn` (per-clause bypass defeats
|
||
compound-command smuggling; reads stay allowed); `@ai_mail` reslopes the
|
||
dispatch wake-back from a `SKIP_SENDERS` blocklist to an `is_owner` allowlist.
|
||
Cross-part verified end-to-end by devpulse with the real resolver (gate 13/13
|
||
incl. compound-smuggle, wake-back owner/non-owner/depth-cap).
|
||
(built by @spawn + @hooks + @ai_mail, verified by devpulse)
|
||
|
||
- **`subcommand_help` seedgo standard — entry points must intercept `<cmd> --help`
|
||
before dispatch (issue #685, split from #665 item 3).** `drone @X <cmd> --help`
|
||
had no framework contract: drone (a router, not a standards enforcer) forwards
|
||
`--help` as a positional, so behavior was per-branch — 8/16 missed, and two
|
||
branches *executed* the subcommand instead of showing help. seedgo now owns the
|
||
contract: a new AST checker flags entry points that don't guard `<cmd> --help`
|
||
(explicit `remaining_args[0]` guard or argparse `parse_known_args`). 21 tests,
|
||
cwd-portable. 7/17 branches comply; the 10 offenders are tracked as a fleet
|
||
migration (#686). (@seedgo, verified devpulse)
|
||
|
||
- **`windows_compat` now detects `os.kill(pid, 0)` liveness probes, not just
|
||
documents them (issue #682).** `os.kill(pid, 0)` resolves to `TerminateProcess`
|
||
on Windows — it *kills* the target instead of probing it. The checker documented
|
||
the anti-pattern but never flagged it in source. A new detector recognizes the
|
||
valid early-return platform guard (so the reference impl `watchdog/agent.py` isn't
|
||
false-flagged) while catching genuinely unguarded sites. 6 tests; verified across
|
||
the fleet (guarded ref passes, 10 offenders caught → fleet migration #684).
|
||
(@seedgo, verified devpulse)
|
||
|
||
- **`append_jsonl` — a sanctioned rotating JSONL writer + a 30-day stale-log sweep
|
||
(issue #673).** Branches wrote `.jsonl` via raw `open('a')`, bypassing prax
|
||
rotation (which was `.log`-only) — unbounded log growth. `from aipass.prax import
|
||
append_jsonl` gives 500 KB / 1-backup atomic (`os.replace`) rotation with zero
|
||
dependency on the prax logging pipeline (recursion-safe for @trigger's event
|
||
handlers), and `drone @prax log-audit sweep` deletes logs older than 30 days
|
||
across system + branch logs. The raw appenders in @backup (1), @hooks (2), and
|
||
@trigger (11 `.log` sites → `.jsonl`, plus downstream medic readers) all adopted
|
||
it — zero raw log appenders remain fleet-wide.
|
||
(@prax + @backup/@hooks/@trigger, verified devpulse)
|
||
|
||
- **Hook engine: Codex bridge + portable test suite (issue #635, DPLAN-0184
|
||
leftovers).** The engine now drives Codex hooks the same way it drives Claude:
|
||
new `handlers/bridges/codex.py` mirrors the claude.py bridge (same
|
||
`EventType:hook_name` dispatch) with Codex protocol normalization — stdin
|
||
remaps `input`→`tool_input`, stdout wraps in the `hookSpecificOutput` envelope
|
||
(`additionalContext` for injection, `permissionDecision` +
|
||
`permissionDecisionReason` for blocks — fixing the known DPLAN-0205 bugs:
|
||
missing reason, wrong field name). And `drone @hooks test` is a portable
|
||
drop-in runner that fires every hook from `.aipass/hooks.json` with mock data
|
||
per event type and reports fired/blocked/disabled/crashed with timing
|
||
(`--verbose` previews output). 23 new tests (12 bridge + 11 runner), seedgo
|
||
31/31 both. (built by @hooks, verified by devpulse)
|
||
|
||
### Fixed
|
||
|
||
- **hooks/bridge: `-p` headless invocations no longer routed through tmux
|
||
(issue #677, DPLAN-0226 fine-tune leftover).** The boot wrapper
|
||
(`session_boot.py`) applied its tmux/session-lookup/live-attach logic to every
|
||
invocation — wrong for `claude -p`, a non-interactive one-shot that never
|
||
registers in `~/.claude/sessions`. The wrapper now detects `-p` in extra_args
|
||
and short-circuits to direct `execvp` of claude — no tmux, no session lookup.
|
||
+5 tests (39 pass). (built by @hooks, verified by devpulse)
|
||
|
||
- **Owner-capability PART 4 — devpulse's `watchdog` + `feedback` now gate on the
|
||
sealed-registry owner, and cross-project (issue #681).** Closes the
|
||
owner-capability model (#678): the last two owner-only tools were still gated
|
||
by a hardcoded `cwd.name == "devpulse"` check — which, it turns out, was a
|
||
**no-op through drone**: drone runs a routed module with `cwd=<branch_path>`,
|
||
so the module's own `Path.cwd()` is *always* the devpulse tree and can't
|
||
identify the caller (a `@flow` caller sailed straight through). A new shared
|
||
`handlers/owner/guard.py` resolves the *real* caller from the env drone sets
|
||
(`AIPASS_CALLER_BRANCH` / `AIPASS_CALLER_CWD`) and checks it against the sealed
|
||
owner via the frozen `is_owner(email, start_path)` contract — so it works in
|
||
any project (devpulse in AIPass, whoever owns elsewhere), not a hardcoded name.
|
||
`feedback send` stays open (it's the inbound channel any agent uses to drop
|
||
feedback to the owner); every mailbox read/manage verb is owner-only. Fail-safe:
|
||
if no owner is sealed yet (old/partial install) or the resolver can't import,
|
||
it falls back to the legacy devpulse-path heuristic so existing installs never
|
||
hard-break. Live-verified end-to-end: owner allowed, `@flow` denied on both
|
||
tools, `send` open. 18 new tests (15 guard + 3 gate), branch audit 100%.
|
||
(built + verified by devpulse)
|
||
|
||
- **seedgo `json_structure` now sanctions `custom_config/` for operator-editable
|
||
config (issue #643).** The standard said "`{branch}_json/` root, one directory,
|
||
no splits" and the checker ignored subdirs, so `custom_config/` (home of
|
||
operator-tunable runtime config like `cadence_config.json`, `memory.config.json`)
|
||
was an undocumented convention. `json_structure_check.py` gained an
|
||
`ALLOWED_JSON_SUBDIRS` allowlist and a `check_branch_post()` that validates
|
||
`{branch}_json/` subdirs — `custom_config/` and hidden dirs (`.archive`) pass,
|
||
any other split is flagged. `json_structure_content.py` documents the directory
|
||
structure and operator-config location. The subdir check honors
|
||
`.seedgo/bypass.json` (bypass rules are threaded through
|
||
`check_branch_post` → `_check_json_dir_structure`), so a branch can sanction a
|
||
legitimate data subdir while unsanctioned + unbypassed splits still fail. 7 new
|
||
tests. (The new check surfaced `devpulse_json/compass/` — the devpulse Compass
|
||
SQLite/FTS5 decision store, which needs its own directory — now sanctioned via a
|
||
documented devpulse bypass; audit confirms Json_Structure back to 100%.)
|
||
|
||
- **`git_gate` block messages now guide external users instead of dead-ending
|
||
(issue #620).** A blocked raw `git`/`gh` command previously just errored. The
|
||
block message now explains *why* git is enforced (prevents cross-agent state
|
||
conflicts), lists the key `drone @git` commands (commit, smart-sync, sync, pr,
|
||
checkout), points to `drone @git --help`, and shows how to disable the gate in
|
||
isolation (`git_gate.enabled = false` in `.aipass/hooks.json`) — verified
|
||
against the engine, which skips a disabled hook per-hook without affecting other
|
||
hooks or `drone @git`. The combined `GIT_GH_REDIRECT` was split into distinct
|
||
`GIT_REDIRECT` + `GH_REDIRECT`; `EDIT_REDIRECT` also shows the disable path. An
|
||
init notice was added to the `project_hooks.json` template and the on/off story
|
||
documented in the hooks README. 6 new tests (86 in `test_git_gate`).
|
||
|
||
- **Telegram `/create` + `/cancel` are now gated to the base @aipass bot (issue
|
||
#644).** Every per-branch bot inherited `BaseBot`'s `/create` + `/cancel` and
|
||
could mint new bots — but Patrick designated the base @aipass bot as the *sole*
|
||
spawner. `base_bot.py` now guards on bot identity (branch bots carry a
|
||
`branch_name`; the base bot's is `None`): `_dispatch_command` returns `False` for
|
||
`create`/`cancel` on a branch bot (falls through to normal handling), and
|
||
`get_custom_commands` advertises them only for the base bot. Rode along in the
|
||
same @skills pass: fail-loud fixes to `botfather_client.py` (issues #669.2/#669.3,
|
||
already closed) — `_load_telethon_config` now raises `RuntimeError` naming the
|
||
config path and the `drone @api set-secret telegram telethon_config` command
|
||
instead of silently returning `None` — plus poll-offset test coverage (#668).
|
||
133 telegram tests pass, seedgo 31/31 on both source files.
|
||
|
||
- **seedgo no longer lints throwaway code (issue #675).** A single disposable POC
|
||
used to fire 8 standard violations (architecture, meta, shebang…). The audit and
|
||
checklist now skip any file resolved under a system temp dir
|
||
(`tempfile.gettempdir()` / `/tmp`, cross-platform) or a `scratchpad` path, and a
|
||
new `--prototype` flag (plus an in-file `# seedgo: prototype` marker in the first
|
||
5 lines) exempts disposable code explicitly. Wired into
|
||
`branch_audit._collect_py_files` (throwaway filter) and `checklist.run_checklist`
|
||
(early-return skip). 6 new tests; live-verified that a `/tmp` file and a
|
||
marker-tagged file both report "✓ (skip)".
|
||
|
||
- **The `claude()` boot shim now ships and installs on onboarding (issue #666).**
|
||
Its installer (`hooks/tools/install_boot_shim.sh`) lived under a gitignored
|
||
`tools/` dir — never version-controlled, never shipped — so the
|
||
attach-if-live / start-in-tmux boot feature (and presence-gate-via-boot) was
|
||
dev-local only; a macOS user could not attach/resume their session. A root
|
||
`.gitignore` negation now tracks exactly that one file (`tools/` re-ignored,
|
||
only the installer whitelisted — README stays out), and `setup.sh` runs it
|
||
right after hook installation (idempotent via a marker check, non-fatal on
|
||
error, venv Python resolved from the script's own location for POSIX/Windows).
|
||
Fresh clones and `aipass install` now get the shim.
|
||
|
||
- **Interactive-occupancy detection is now cross-platform — the wake-back guard
|
||
no longer goes blind on macOS (issue #680).** `_is_branch_occupied()` and
|
||
`_read_session_type()` (duplicated in `dispatch/wake.py` and `dispatch/daemon.py`)
|
||
read `/proc/{pid}/cwd` + `/proc/{pid}/environ`, which do not exist on macOS —
|
||
so occupancy always resolved `False` there and an external wake-back could spawn
|
||
a *second* Claude session on an already-interactive branch (double-session,
|
||
weakening the TDPLAN-0012/#678 interactive-dispatcher guard). The per-PID cwd
|
||
and session-type probes are now extracted into platform helpers: `_get_pid_cwd`
|
||
(Linux `/proc` readlink, macOS `lsof -a -p PID -d cwd -Fn`) and
|
||
`_read_session_type_darwin` (`ps -p PID -wwE`), applied identically in both
|
||
files. Fail-safe: an unreadable cwd/env logs at info and continues — never
|
||
crashes the wake path. +11 tests (macOS cwd, macOS session type, zombie,
|
||
unsupported platform), seedgo 31/31 on both files.
|
||
|
||
- **SubagentStop gate no longer runs its ~600ms seedgo check on every internal
|
||
turn (issue #606).** Claude Code creates an internal agent per response turn
|
||
with an empty `agent_type`, so the `subagent_gate` handler was firing its full
|
||
`drone @git status` + seedgo modified-files check on every turn, not just when a
|
||
real Agent-tool sub-agent completed. `handle()` now early-returns `_ALLOW` when
|
||
`agent_type` is empty; the full check runs only for a real sub-agent
|
||
(non-empty `agent_type`). Piper speech is a separate notification hook and is
|
||
unaffected — the trust layer stays visible. 3 new tests (empty skip, missing-key
|
||
skip, real-agent full check), 17/17 green.
|
||
|
||
- **Watchdog stall detector no longer false-fires on a long single tool call, and
|
||
a real stall now reaches devpulse live (issue #634).** Liveness was inferred
|
||
purely from JSONL file-size growth, so an agent doing one genuinely long
|
||
operation (big read, long-running Bash, heavy compute) wrote no new lines for
|
||
the span and was misread as `STALLED` while actively working. `watch_agent` now
|
||
also treats an in-flight `tool_use` (the assistant's last transcript entry while
|
||
a tool runs) as activity — verified live against real Claude Code transcripts:
|
||
the `tool_use` line is written at tool *start* and persists for the whole call.
|
||
Part 2: the stall (and a new long-running-tool advisory, plus a resumed signal)
|
||
is emitted to **stdout** — which the Monitor-tool wrapper surfaces as a live
|
||
event — instead of only `stderr`+logger, which Monitor captures but never
|
||
relays. Stall logic extracted into a `StallTracker` for clarity; +9 tests
|
||
(142 green), devpulse audit 100%. (devpulse)
|
||
|
||
- **`aipass install` shows progress during the slow dependency build, and a README
|
||
quick-start command is corrected (issue #665, items 6–7).** The editable install of
|
||
the `[memory]` extras ran with `pip --quiet`, going silent for minutes during wheel
|
||
builds — it looked hung; dropped `--quiet` on that step and set expectation in the
|
||
echo. And `README.md` showed `drone @seedgo audit my_project`, which fails
|
||
(`audit` takes a registered pack name) — corrected to `audit aipass`. Remaining
|
||
#665 items (version, --help names, subcommand --help, placeholders, hints, crash-vs-
|
||
unknown) span multiple owners and stay open. (devpulse)
|
||
|
||
- **`aipass`/`drone` first-contact papercuts resolved — issue #665 fully closed
|
||
(items 1, 2, 4, 5, 8).** `aipass --version` now reads package metadata (was
|
||
hardcoded `0.1.0`); `aipass --help` lists real `COMMAND` names, not file stems
|
||
(`help` not `help_chat`, `init` not `init_flow`); a crashing or unimportable
|
||
handler surfaces its real cause instead of `Unknown command` (@aipass). `drone
|
||
systems` placeholder descriptions now derive from each branch's passport/README,
|
||
fixed in code so they survive registry regen — the earlier gitignored data edit
|
||
didn't (@spawn). Bare-mode hints point to working commands — `drone @daemon
|
||
--help` (there is no `daemon` binary) and the standard `drone @memory --help`
|
||
(@daemon, @memory). Item 3 became the #685 standard. (multi-branch, verified devpulse)
|
||
|
||
- **`os.kill(pid, 0)` liveness probes across the fleet are now Windows-safe (issue
|
||
#684).** On Windows `os.kill(pid, 0)` maps to `TerminateProcess` — the "probe"
|
||
kills the target. Nine sites across @ai_mail (dispatch daemon/wake), @drone (git
|
||
lock handler), @flow (runner lock), @hooks (cc_sessions/presence) and @devpulse
|
||
(watchdog registry) now early-return to an `OpenProcess` + `GetExitCodeProcess`
|
||
check on win32, mirroring the `watchdog/agent.py` reference. The #682 checker
|
||
confirms 0 unguarded sites remain (down from 10); the last one,
|
||
`tools/git_lock_tool.py`, is split to #687 (blocked by the tool's pre-existing
|
||
gate debt). (fleet migration, verified devpulse)
|
||
|
||
- **Telegram poll loop no longer re-drains a rate-limited backlog; systemd
|
||
suicide-loop + silent config fallback fixed (issues #668, #669).** #668: the poll
|
||
loop advanced the update offset *after* processing, so a rate-limited/erroring
|
||
update never advanced it — the same backlog re-fetched in a flood loop. The
|
||
offset now advances *before* `process_update`, so a consumed update never pins
|
||
it. #669: (1) systemd unit gets `KillMode=process` so a restart isn't killed by
|
||
the old instance's cgroup teardown (suicide-loop); (2) `create_bot_via_botfather`
|
||
now **raises** with an actionable message (naming the `set-secret` fix) instead of
|
||
silently returning `None` when telethon config is missing (fail-honestly);
|
||
(3) stale config-mechanism docstrings corrected. Also Windows-hardened
|
||
`_is_pid_alive`/`_check_lock` and switched `TEMP_DIR` to `tempfile.gettempdir()`.
|
||
653 telegram tests green. (@skills, verified devpulse)
|
||
|
||
- **Rollover `_find_repo_root` now fails loud, and `edit_gate` warns on over-count
|
||
memory sections (issue #683, #664 follow-up).** The PreCompact rollover hook's
|
||
`_find_repo_root` returned `None` silently when `AIPASS_HOME`/cwd was wrong — the
|
||
exact silent-skip that hid #664 for months; it now logs a `logger.error` with the
|
||
`AIPASS_HOME` value and cwd before returning. And `edit_gate` enforced per-entry
|
||
*character* caps but not entry *counts*, so a branch could drift past its count
|
||
cap between rollovers; a soft `_check_section_counts` now warns (never blocks),
|
||
reading the same `memory.config.json` rollover caps @memory uses. +14 tests
|
||
(70 green); both live-proven (bad root → error logged; over-cap → warn, no block).
|
||
(@hooks, verified devpulse)
|
||
|
||
- **`is_owner()` now case-folds — `is_owner('DEVPULSE')` matches `is_owner('devpulse')`
|
||
(issue #679).** The spawn-registry resolver (`registry.py:382`) `@`-normalized the
|
||
email but never lowercased, so a mixed-case branch name (registry names are
|
||
mixed-case: `DEVPULSE` vs `devpulse`) returned `False` against the seated owner.
|
||
Harmless today (the only caller lowercases first) but the frozen TDPLAN-0012
|
||
contract promises normalization, and PART-4 owner-gating may pass a raw name.
|
||
Now lowercases both sides; verified live (every case variant of the owner → True,
|
||
non-owners → False) + a case-insensitivity test (316 green). (@spawn, verified devpulse)
|
||
|
||
- **`aipass install` no longer hard-fails (exit 2, silently) when it can't create
|
||
global symlinks (issue #660 follow-up).** `setup.sh` runs under
|
||
`set -euo pipefail`; the #660 `safe_symlink` refactor returns `2` on `ln`
|
||
failure, but the call sites captured that code on the *next* line (`rc=$?`), so
|
||
`set -e` killed the installer at the symlink step — before the `~/.local/bin`
|
||
fallback (built for exactly the no-sudo case) could run. Any sudo-less
|
||
environment (containers, CI, locked-down machines) got a silent exit 2 with no
|
||
symlinks, despite an otherwise-complete install. Fixed all three call sites to
|
||
`rc=0; safe_symlink … || rc=$?` (set-e-safe). Proven in docker: a sudo-less
|
||
install now falls back to `~/.local/bin` and exits 0. (devpulse)
|
||
|
||
- **`drone @devpulse watchdog agent` no longer reports failure on a successful
|
||
watch (issue #661).** Its "invoke via Monitor tool" reminder was printed
|
||
through `cli.error()`, which — after the #661 exit-code work — trips a
|
||
process failure flag, so every successful watch exited non-zero with a red X.
|
||
Rerouted to a dim console note; genuine argument errors still `error()` →
|
||
exit 2. (devpulse)
|
||
|
||
- **The prax monitor now holds a single-instance lock, so a duplicate/orphan
|
||
monitor can't double-send Telegram relay messages (issue #671).** A new
|
||
`instance_lock` handler writes a pidfile (`prax_json/monitor.pid`, outside the
|
||
tailed `system_logs/`) with a liveness check: `acquire()` runs before relay
|
||
init and refuses to start (fail-loud, naming the holding PID) if a live monitor
|
||
already holds the lock, reclaims a stale pidfile when the recorded PID is dead,
|
||
and `release()` clears it on shutdown. The liveness probe is platform-branched
|
||
— POSIX `os.kill(pid, 0)`, Windows `OpenProcess`/`GetExitCodeProcess` (a raw
|
||
`os.kill(pid, 0)` *terminates* the target on Windows). `monitor.py` was also
|
||
split under the 600-line limit (`pid_cache` extracted). +25 tests.
|
||
(built by @prax, verified by devpulse)
|
||
|
||
- **`aipass init update` now refreshes `AGENTS.md` and prunes stale managed
|
||
cruft (issue #676).** Two gaps: (1) `update_project` synced `AGENTS.md` from a
|
||
`.aipass/project_AGENTS.md` template that never existed, so the branch silently
|
||
no-op'd and `AGENTS.md` was never refreshed on update (only `CLAUDE.md`, whose
|
||
template exists, synced) — added the template and reconciled create/update to
|
||
one source; (2) the update was additive-only — added a whitelist-scoped cleanup
|
||
pass (`_STALE_MANAGED_FILES`, currently the retired `aipass_global_prompt.md`)
|
||
that removes only positively-identified managed artifacts, logs every removal,
|
||
and never touches user-owned files. The template also had to be un-ignored in
|
||
`.aipass/.gitignore` (allowlist) or it would never have shipped — caught in
|
||
verify. +7 tests; live repro confirms update emits `AGENTS.md` and clears a
|
||
planted cruft file. (built by @aipass, verified by devpulse — incl. the
|
||
gitignore ship-gap)
|
||
|
||
- **External-project branches now auto-roll — rollover discovery is no longer
|
||
cwd-scoped (issue #664).** Branch discovery only saw registries reachable by
|
||
walking up from the caller's cwd, so branches living solely in an external
|
||
project's `*_REGISTRY.json` were never reached by rollovers fired from the
|
||
AIPass tree (the PreCompact hook runs with cwd = repo root) — their `.trinity`
|
||
files grew unbounded (one hit 110 key_learnings against a 15 cap) and vector
|
||
stores went stale. `@memory` added a persisted `known_registries.json`
|
||
(gitignored per-install data) that records every external registry seen via the
|
||
cwd walk, so discovery reaches them regardless of caller cwd; stale/deleted
|
||
registry paths are filtered on load. Plus a soft entry-**count** guard at write
|
||
time (warns, never blocks) since the write gates only enforced char caps. The
|
||
remaining hooks-side harden (`_find_repo_root` fail-loud + the `edit_gate`
|
||
count-guard) is filed for `@hooks`. +12 tests; live repro confirms a rollover
|
||
fired from the AIPass root now reaches an external-registry branch.
|
||
(built by @memory, verified by devpulse)
|
||
|
||
---
|
||
|
||
## [2026-07-09]
|
||
|
||
### Added
|
||
|
||
- **Exit-code contract foundation — failing commands can now exit non-zero
|
||
(issue #661, in progress).** CLI error paths printed an error but returned exit
|
||
`0`, so `$?`-checking callers (core to running `drone` as a subprocess) were
|
||
told success on failure. The dispatch contract was a 2-state bool (`handled` /
|
||
`not-mine`) with no way to say "handled *and* failed". `@cli` now exposes a
|
||
process-level failure flag + `resolve_exit(handled)` (→ `0`/`1`/`2`), and
|
||
`error()` auto-trips the flag — so any failure routed through `error()` gets a
|
||
correct non-zero exit with zero per-site edits, and it can't regress. Inert
|
||
until a branch's `main()` adopts it. `@seedgo` added an `output_routing`
|
||
standard (39th checker) flagging user-facing status output that bypasses the
|
||
cli helpers — 254 sites across 14 branches, the migration checklist. `devpulse`
|
||
is the first adopter (`main()`→`resolve_exit`, feedback migrated to `error()`,
|
||
exit `2`/`0`/`1` verified, 100% seedgo). Fleet migration to follow.
|
||
(built by @cli + @seedgo)
|
||
|
||
### Fixed
|
||
|
||
- **`@trigger` no longer rewrites its 44KB `trigger_data.json` on every log event
|
||
(issue #674).** The branch log watcher persisted dedup hashes and log positions
|
||
with two separate full-file rewrites *per event*, so a log burst churned the
|
||
file ~1-2×/sec (surfaced by prax monitoring). Replaced the per-event/counter
|
||
writes with a debounced coalescing writer: events set a dirty flag and both
|
||
keys are written in a single atomic write at most once per 5s, with a forced
|
||
flush on watcher stop so nothing is lost on clean shutdown. Also confirmed the
|
||
retired `bulletin_created` event handler no longer loads or warns (it lives in
|
||
`.archive/` with no live references; scrubbed stale README/bypass mentions).
|
||
564 trigger tests green (+6 debounce tests).
|
||
|
||
- **`aipass install` no longer silently repoints your global `drone`/`aipass`
|
||
symlinks (issue #660).** `setup.sh` force-overwrote the global CLI symlinks with
|
||
`ln -sf` on every run, no check and no opt-out — so `aipass install
|
||
--path /tmp/scratch` "to try it" silently hijacked your real global commands to
|
||
the scratch tree, which broke them once `/tmp` cleared, disconnected from the
|
||
cause. A new `safe_symlink` guard refuses to repoint a symlink that points at a
|
||
*different* install: it prints a loud from→to warning and leaves the existing
|
||
link untouched unless you pass `--force-symlink`; `--no-symlink` opts out of
|
||
symlinking entirely. Both flags thread through `aipass install`. Fresh installs
|
||
and same-location reinstalls behave exactly as before. Adds a `safe_symlink`
|
||
regression test (`tests/setup_symlink_guard_test.sh`) and 3 flag-forwarding
|
||
tests; the touched install output was migrated to `@cli` helpers (#661).
|
||
|
||
- **`drone @flow close` no longer reports a false "timed out after 30s" on a
|
||
successful close (issue #662).** A single-plan close committed early (plan
|
||
marked closed, file archived) and then ran memory vectorization
|
||
*synchronously* — `drone @memory process-plans` — inline. On the cold first
|
||
close of a session that crossed drone's 30s executor timeout, so drone killed
|
||
the flow subprocess and returned exit `1` **after** the close had fully
|
||
committed. An autonomous agent reading that exit code would retry or abandon an
|
||
already-closed plan. `close_plan_impl` now honors its long-existing
|
||
`spawn_background` flag: single close fires the already-detached
|
||
`_spawn_background_runner` (the same path `close_all` uses) and returns
|
||
immediately after archive; vectorization runs in the background. Also removed
|
||
the handler's cross-handler imports (archive/trigger now injected). Verified
|
||
live: a real close returns in ~5s at exit 0 ("Vectorizing in background") vs
|
||
the prior 30s-timeout risk. 730 flow tests green (+2 new).
|
||
|
||
- **`aipass doctor` no longer hangs on non-interactive stdin (issue #663).** The
|
||
auto-wire `[y/N]` prompt called `input()` with no tty guard, so a caller with a
|
||
blocking-but-idle stdin (a script, CI job, or subprocess whose stdin never
|
||
sends EOF) hung `doctor` indefinitely — reading as a crash from the flagship
|
||
"check my system" command a new user runs first. `prompt_auto_wire` now guards
|
||
the prompt with `sys.stdin.isatty()`: a non-tty stdin declines the auto-wire
|
||
(prints the manual-wire warning) instead of blocking. Verified against the
|
||
exact repro — a blocking non-tty stdin that never EOFs now completes instead of
|
||
hanging until killed. Adds 3 regression tests.
|
||
|
||
- **macOS session lock-out: the boot wrapper can now see tmux sessions on
|
||
macOS.** `session_boot` decided whether a live Claude session lived inside
|
||
tmux by walking the process tree through `/proc/<pid>/status` — Linux-only.
|
||
On macOS (no `/proc`) that walk always failed, so the wrapper concluded every
|
||
live session was "outside tmux" and refused to attach, locking the user out of
|
||
their own session in an unbreakable loop. Replaced the `/proc` read with a
|
||
portable `ps -o ppid=` ancestry walk (Linux + macOS). Also: both the boot
|
||
warning and the presence-gate block now spell out the exact recovery command
|
||
(`kill <pid> && claude`, `command claude --resume`) instead of a vague "kill it
|
||
first", and the wrapper no longer doubles `--permission-mode` when the user
|
||
passes it explicitly. New/updated tests, hooks suite 791 green. (built by @hooks)
|
||
- **Boot-shim installer no longer bakes a hardcoded user path.**
|
||
`install_boot_shim.sh` hardcoded `/home/patrick/Projects/AIPass/.venv/bin/python`
|
||
into the `claude()` shell function — wrong on any other machine or user. It now
|
||
resolves the venv interpreter from the script's own location (POSIX
|
||
`.venv/bin/python`, Windows/git-bash `.venv/Scripts/python.exe`, else PATH
|
||
`python3`) and bakes the correct one at install time.
|
||
- **Silent hook-wiring break: provider settings could be left half-wired with no
|
||
warning.** A stale `setup.sh` merge orphaned the `SessionStart` hook event to an
|
||
empty `[]` — the key existed but nothing fired — written silently, and it went
|
||
unnoticed for weeks because CI skips the provider-settings snapshot test (it
|
||
needs `~/.claude/settings.json`, absent in CI). Root cause: the merge stripped
|
||
every AIPass bridge entry per event, then re-added only events still present in
|
||
its own hook list, orphaning any event it no longer defined. The merge now drops
|
||
such an event entirely (and says so) instead of emitting an empty array. Also
|
||
corrected the stale snapshot fixture (dropped the dormant `presence_gate`, which
|
||
by design ships wired only in project config, and added
|
||
`SessionStart:cadence_reset`) and marked `presence_gate` `provider_wired: false`
|
||
so the wiring checker knows it is intentionally not provider-wired.
|
||
- **`json_handler.load_json` crashed on an empty/whitespace file (#667).** Under
|
||
concurrent audit + tests a writer could truncate a JSON file in the window
|
||
between `ensure_json_exists` and `load_json`'s own read, raising
|
||
`JSONDecodeError`. `load_json` now guards an empty/whitespace read and falls back
|
||
to the type's default template; a non-empty but malformed file still raises (fail
|
||
honestly). 3 new tests, red-green proven.
|
||
|
||
### Added
|
||
|
||
- **`drone @hooks verify` — hook-wiring integrity checker.** Cross-checks
|
||
`~/.claude/settings.json` against `.aipass/hooks.json` and fails loud on empty
|
||
provider hook arrays, orphaned entries, enabled handlers with no provider bridge,
|
||
and duplicate (matcher-aware) entries — so a half-wired hook can never rot
|
||
silently again. `aipass doctor` now runs this check under Services and re-verifies
|
||
after `--fix`. 40+ new tests. (built by @hooks + @aipass)
|
||
|
||
## [2026-07-07]
|
||
|
||
### Fixed
|
||
|
||
- **Drive sync now respects `.backupignore` on the sync path.** The ignore spec
|
||
was applied at backup time only — anything already inside `.backup/versioned/`
|
||
got uploaded regardless. Real case: Vera-Studio's store carried 37K legacy
|
||
`node_modules` files (92% of the store), turning a KB-sized sync into a 7-8
|
||
hour crawl (Drive uploads are per-file API round-trips — latency-bound, not
|
||
bandwidth-bound; the clean store syncs in ~13 min). `drive_sync` now re-filters
|
||
store files through the project's `.backupignore` before upload and logs the
|
||
ignored count. Also fixed: `json_handler.log_operation` crashed on `Path`
|
||
objects (`PosixPath is not JSON serializable`) — now serializes with
|
||
`default=str`. 2 new tests, backup suite 247 green. (built by @backup)
|
||
|
||
### Added
|
||
|
||
- **Fresh-context grounding: cadence reset on new chat / clear / compact.**
|
||
Both prompt loaders (tier0 kernel + navmap) now run at period 5, and a new
|
||
`SessionStart` hook resets the cadence counter on `startup`/`clear` (skips
|
||
`resume` — restored context already carries grounding; `compact` was already
|
||
reset via PreCompact). Net effect: the first message of every fresh context
|
||
gets full grounding, then every 5th turn after. Wired end-to-end: handler
|
||
(`session_start.py`), project config (`.aipass/hooks.json` + the
|
||
`project_hooks.json` template for external projects), and `setup.sh` seeds
|
||
the provider `SessionStart` entry for new installs. Proven end-to-end from a
|
||
real fresh-user clone of dev in Docker — 19/19 assertions via the new
|
||
`tests/docker_dev_verify.sh` (bridge-era; supersedes the stale
|
||
`docker_clone_test.sh`). (built by @hooks + @devpulse)
|
||
|
||
### Fixed
|
||
|
||
- **`aipass` ≠ drone-routed — misroutes now guide instead of crash.** `aipass`
|
||
is the user's front-door CLI, deliberately not resolvable by drone. But
|
||
`drone aipass` misdirected, `drone @aipass` crashed with a traceback, and
|
||
`aipass @drone` dead-ended. All three now print clear guidance (what aipass
|
||
is, what drone is, how to reach each). Kernel + navmap prompts updated so
|
||
agents know the exception. (built by @drone + @aipass)
|
||
|
||
---
|
||
|
||
## [2026-07-06]
|
||
|
||
### Fixed
|
||
|
||
- **prax log watchdog now covers branch `logs/` dirs — `.jsonl` runaway growth
|
||
caught.** Rotation was hardcoded to `.log` files, and several branches write
|
||
`.jsonl` logs via raw `open(path, "a")` appenders that bypass prax entirely —
|
||
`hooks/logs/engine.jsonl` had grown to 63 MB, `backup/logs/operations.jsonl`
|
||
to 31 MB, `trigger/logs/medic_suppressed.log` to 7 MB, all unrotated. The
|
||
log-watchdog safety net also only scanned `system_logs/*.log`. @prax extended
|
||
it: `scan_branch_log_files()` sweeps every `src/aipass/*/logs/` for `.log` +
|
||
`.jsonl` (WARN at 1 MB unrotated, CRITICAL at 10 MB),
|
||
`enforce_branch_log_limits()` truncates flagged files to the last 5000 lines,
|
||
and `drone @prax log-audit` now reports both system and branch scopes. 11 new
|
||
tests, full prax suite 947 green. The raw-appender writers themselves still
|
||
need per-owner caps — routed to @hooks, @backup, @trigger. (built by @prax)
|
||
|
||
---
|
||
|
||
## [2026-07-05]
|
||
|
||
### Fixed
|
||
|
||
- **HVTrust badge restored in the root README.** hvtracker corrected the
|
||
methodology v4.1 grade-computation bug (issue #109); the badge shows the
|
||
right grade again, so the temporary comment-out from earlier today is
|
||
reverted.
|
||
|
||
- **Installer no longer destroys a user's custom Claude Code hooks (DPLAN-0234
|
||
Strand C).** setup.sh used to write `settings["hooks"]` wholesale — anyone
|
||
with their own hooks in `~/.claude/settings.json` lost them on install or
|
||
re-run. Now it merges: every AIPass bridge entry (identified by the
|
||
`bridges/claude.py` marker) is refreshed, while user-wired hooks and custom
|
||
events are preserved. Verified against fixtures: custom hooks survive, stale
|
||
AIPass entries are replaced without duplicates, and the fresh-install output
|
||
is shape-identical to before (7 events, 6 UserPromptSubmit + 6 PreCompact
|
||
entries). Found while fire-testing a fresh install's hooks in Docker — all
|
||
17 wired hook entries pass on a cold Linux clone (real kernel/navmap/branch
|
||
prompt bytes, git gate blocks, clean no-ops on empty state).
|
||
|
||
- **Windows fresh installs get a working hook bridge.** setup.sh wrote the
|
||
Claude bridge command with `.venv/bin/python3` on every OS — but Windows
|
||
venvs put the interpreter at `.venv/Scripts/python.exe` and have no `bin/`,
|
||
so hooks on a fresh Windows install pointed at a nonexistent python and
|
||
would never fire. The bridge string is now OS-aware (bash passes
|
||
`IS_WINDOWS` into the hook-install step). @hooks assessed the rest of the
|
||
chain: `$AIPASS_HOME` expansion works on Windows because Claude Code runs
|
||
hooks via Git Bash (which must exist for setup.sh to have run), and the
|
||
bridge itself has zero POSIX assumptions — the interpreter path was the
|
||
only gap. Verified: both OS modes produce the right bridge string, merge
|
||
marker unchanged, custom-hook preservation intact. (assessed by @hooks)
|
||
|
||
### Added
|
||
|
||
- **`./aipass` — repo-root cold-clone launcher (DPLAN-0234 Strand B).** The
|
||
branded entry point for the clone-first flow: `git clone`, `cd AIPass`,
|
||
`./aipass install` — three commands to a working AIPass. Stdlib-only bash
|
||
(zero deps, runs before anything is installed): pre-setup, only the `install`
|
||
verb exists and delegates to `setup.sh` with full flag pass-through
|
||
(`--no-init` / `--with-init` / `--project`); any other verb prints help
|
||
pointing at `./aipass install`. Post-setup the launcher turns transparent —
|
||
it execs the venv `aipass` binary for everything, so `./aipass doctor` just
|
||
works. Bare `aipass` always resolves to the PATH binary; the launcher only
|
||
ever runs as an explicit `./aipass`. 13 launcher tests (file properties,
|
||
pre-setup help, install delegation, post-setup forwarding), @aipass suite
|
||
622 green, seedgo 100%. README Quick Start now leads with `./aipass install`.
|
||
(built by @aipass)
|
||
|
||
- **`./setup.sh` chains into `aipass init run` — clone-first one-command install
|
||
(DPLAN-0234 Strand A).** Distribution is git-clone, not pip: the framework
|
||
changes constantly, so a PyPI snapshot goes stale while a clone is always
|
||
current HEAD. Now `git clone && cd AIPass && ./setup.sh` takes you from cold
|
||
clone to a working first project in one command: on interactive terminals,
|
||
setup ends by launching the guided `aipass init run` in a sibling directory
|
||
(default `~/aipass-project`, prompt to choose — init refuses to run inside the
|
||
engine tree). New flags mirror `aipass install`'s handoff rules: `--no-init`
|
||
skips, `--with-init` forces even headless (init chains `--non-interactive`),
|
||
`--project <dir>` picks the target. CI and piped shells skip automatically
|
||
(`CI` env or no tty), so the windows/macos-test workflows that run bare
|
||
`bash setup.sh` are untouched. `install.py` now calls `setup.sh --no-init`
|
||
since install owns its own init handoff — no double-scaffold. Proven in
|
||
clean-room Docker, both runs exit 0: bare headless run skips init with a
|
||
hint; `--with-init` run chains init to `✓ Project initialized.` with the
|
||
project dir scaffolded. 39/39 install tests, seedgo 30/30. README Quick
|
||
Start updated to the one-command flow.
|
||
|
||
- **`aipass install` — one-command framework bootstrap.** The missing half of
|
||
`pip install aipass`: a single command resolves the install home (default
|
||
`~/AIPass`), git-clones the public repo, runs `setup.sh` (venv, editable
|
||
install, hook wiring), verifies the toolchain, and auto-launches `aipass init`
|
||
in the same terminal — so `pip install aipass && aipass install` bootstraps
|
||
the whole system with nobody the wiser. New auto-discovered `install.py`
|
||
module (zero shared-code edits) with flags `--non-interactive / --path /
|
||
--here / --no-init / --with-init / --project / --dry-run`; 39 unit tests,
|
||
seedgo 30/30, @aipass suite green. Proven in a clean-room Docker image
|
||
(nothing pre-baked) across two runs, both exit 0: `pip install` (local wheel)
|
||
→ clone → `setup.sh` (17 branches registered, 13 bootstrapped, hooks wired
|
||
into `~/.claude/settings.json`, `AIPASS_HOME` set, `drone`/`aipass` on PATH) →
|
||
live `drone systems`, and `--with-init` chaining straight into `aipass init`
|
||
to completion. Ships install progress bars, an install→init handoff, and
|
||
doctor coverage. (built by @aipass, DPLAN-0233 — PyPI release bump pending)
|
||
|
||
### Changed
|
||
|
||
- **HVTrust badge temporarily hidden in the root README.** hvtracker's
|
||
methodology v4.1 recalibration is miscomputing the grade (showing D/~10 while
|
||
the detail-page dimensions sum to ~78); the badge is commented out until it's
|
||
corrected. Filed upstream as hvtracker issue #109 — restore when resolved.
|
||
|
||
- **devpulse branch prompt — sole-git-writer clarity.** Added a note to the git
|
||
section: because no other agent can commit, merge, or push anywhere, dirty
|
||
cross-branch files are always someone's live WIP, safe to leave and pick up
|
||
later — never a loose end needing handoff.
|
||
|
||
## [2026-07-03]
|
||
|
||
Post-2.6.1 cycle — **unreleased** (held for a later merge).
|
||
|
||
### Changed
|
||
|
||
- **All 17 branches now pass the standards audit at 100% — Windows-compat
|
||
hardening across the board.** Added `sys.stdout/stderr.reconfigure()` UTF-8
|
||
guards (getattr form) to every Rich/CLI entry point, and platform-branched
|
||
POSIX-only subprocess kwargs (`start_new_session` → `CREATE_NEW_PROCESS_GROUP`
|
||
on win32). The seedgo `windows_compat` checker now credits the getattr guard
|
||
form (not just direct `.reconfigure()` calls), with a locking regression test.
|
||
Swept per-branch via dispatch; checker fix by @seedgo. Verified by a full
|
||
17/17 audit (pyright clean).
|
||
|
||
### Fixed
|
||
|
||
- **Telegram replies no longer overwrite the previous message.** The Stop-hook
|
||
out-path (`hooks/.../notification/telegram_response.py`) reused a stale
|
||
`processing_message_id`: after a successful delivery, `_advance_pending` kept
|
||
the pending file but never cleared the placeholder id, so any reply that fired
|
||
without a fresh "Processing…" bubble (remote/mirror input, multi-Stop turns)
|
||
re-*edited* the same Telegram message instead of posting a new one — every
|
||
response clobbered the last. Now clears `processing_message_id` after the first
|
||
delivery, so subsequent Stops fall through to `_send_with_retry` (a new
|
||
message). Root-caused live on the devpulse bot and proven by the delivery log
|
||
flipping `edit`→`send`; +2 regression tests in `TestAdvancePending` (114/114).
|
||
(fixed by @hooks, `f42a98b`, PR #651 — not yet merged)
|
||
|
||
- **`template` audit checker no longer false-flags documentation *about* templates.**
|
||
The advisory stale-template checker (`seedgo/.../template_check.py`) matched its
|
||
marker strings anywhere in a file, so it fired on prose and code that merely
|
||
*mention* the markers rather than on un-rendered stubs — flagging 5 branches,
|
||
only 3 of them real. Three root causes, all fixed:
|
||
(1) it globbed **`.trinity/*.json`**, scanning live memory (`local.json`,
|
||
`observations.json`) that naturally accumulates marker mentions (seedgo's own
|
||
note "Detects NEEDS CONFIGURATION", prax's note about `template_pusher` restoring
|
||
`{{BRANCHNAME}}`); now scans **`passport.json` only**, the sole spawn-templated
|
||
trinity file.
|
||
(2) the single-curly `{…}` regex ran on every `.md` and matched inline JSON /
|
||
f-strings / code paths in READMEs (`{"new": 3}`, `{e}`, `apps/plugins/{name}/`);
|
||
now runs on the branch **prompt only** (the spawn README template has no
|
||
single-curly placeholders).
|
||
(3) the definitive-marker scan matched `{{BRANCH}}` inside markdown inline code —
|
||
e.g. spawn's README documenting ``Replace `{{BRANCH}}`…``, which is scaffolding
|
||
docs, not a stub. For `.md` files, fenced + inline code is now stripped once up
|
||
front before **both** scans (`passport.json` still scans raw). Safe because real
|
||
stubs carry markers in prose/headings (the `## Status: NEEDS CONFIGURATION`
|
||
line), never exclusively in code.
|
||
Verified system-wide: `Template` avg **80% → 94%**, the two pure false positives
|
||
(seedgo memory, spawn README) cleared to `100%`, only the three genuine
|
||
unconfigured prompt stubs (cli/drone/prax) still flag. +7 tests (24/24), full
|
||
suite green. (fixed by @seedgo across 3 dispatched passes, verified by @devpulse)
|
||
|
||
- **cli / drone / prax branch prompts configured** (were spawn stubs). The three
|
||
branches the template checker correctly flagged had never had their
|
||
`.aipass/aipass_local_prompt.md` filled in — they booted with a `NEEDS
|
||
CONFIGURATION` placeholder and no branch-specific identity. Each branch wrote its
|
||
own real prompt (identity, key commands, architecture, critical rules,
|
||
integration points; ~63–67 lines, `PROMPT_STYLE.md` format); all three now score
|
||
`Template 100%`. (written by @cli/@drone/@prax, dispatched + verified by @devpulse)
|
||
|
||
## [2026-07-02]
|
||
|
||
Released as **2.6.1**. Rolls up the DPLAN-0226 / FPLAN-0289 / TDPLAN-0010 /
|
||
FPLAN-0298 batch (unified Telegram↔Claude Code bridge, single-session presence
|
||
gate, live Telegram streaming, `aipass init` template selector + portability,
|
||
`@backup share`) — all documented under `[2026-07-01]` — plus the CI
|
||
stabilization below.
|
||
|
||
### Added
|
||
|
||
- **`drone @git tag <vX.Y.Z>` — guarded release-tag automation (post-2.6.1).**
|
||
Devpulse-tier verb that pushes a release tag with no manual step: fetches
|
||
`origin`, refuses unless the tag's `X.Y.Z` matches **both** `pyproject.toml`
|
||
and `src/aipass/__init__.py` on `origin/main` (version guard) and the tag
|
||
doesn't already exist (exists guard), then tags `origin/main` and pushes —
|
||
firing `publish.yml`. `drone @git tag --list` lists tags. Removes the merge
|
||
playbook's last manual `git tag`/`push` step, so releases need zero user
|
||
input. (built by @drone, S274)
|
||
|
||
### Fixed
|
||
|
||
- **CI green — six regressions from the DPLAN-0226 / FPLAN-0289 / TDPLAN-0010
|
||
batch (PR #646).** The dev branch had gone red across `seedgo-audit`, the
|
||
`test` matrix, and Windows; root-caused and fixed at source:
|
||
- **seedgo** — the new `template_check` advisory checker was gating CI.
|
||
`branch_audit.py` averaged *all* checker scores into the branch total, so
|
||
`template_check`'s `ADVISORY=True` was never honored and it dragged 7
|
||
branches below the 100% floor on legitimate README brace-examples. Added a
|
||
`gating_scores` filter that excludes `ADVISORY is True` checkers before
|
||
computing the average (strict `is True` to avoid MagicMock false-positives)
|
||
and exposed `advisory_standards` in the audit output. Also refreshed the
|
||
provider hooks snapshot fixture to include the `presence_gate`
|
||
`UserPromptSubmit` hook (FPLAN-0289), fixing 4 `test_hooks_snapshot` tests.
|
||
- **hooks** — `cc_sessions.py` (added by the bridge, `f6cbe34`) was missing
|
||
its README entry and a seedgo `modules` bypass (it reads external
|
||
`~/.claude/sessions/*.json`, not branch data, so `json_handler` is the wrong
|
||
tool — same precedent as `presence.py`). Added both.
|
||
- **spawn** — retired the `passport(disabled).py` / `passport_ops(disabled).py`
|
||
pair to `.archive/`; the `(disabled)` suffix kept them visible to the type
|
||
checker, which flagged a broken cross-import between them.
|
||
- **ai_mail** — `test_child_inherits_broker_fd` gave its throwaway test branch
|
||
a real `.trinity/passport.json` so the broker's new `.trinity`-marker
|
||
resolution (`f914ab6`) can resolve it and permit the delete.
|
||
- **spawn** — the `builder→aipass_framework` template rename (`13463c0`) left
|
||
`.gitignore` exceptions pointing at the old `templates/builder/` path, so
|
||
`DASHBOARD.local.json` + ~10 other template files were silently untracked
|
||
since the rename — present on disk (dirty tree passed) but absent from clean
|
||
clones/CI, so `test_full_spawn` failed only in a clean checkout. Fixed all 23
|
||
`.gitignore` exception paths and committed the now-visible template
|
||
scaffolding.
|
||
- **skills** — `test_streaming` asserted a `+1` newline byte, but `write_text`
|
||
text mode translates `\n`→`\r\n` on Windows (2 bytes), failing `windows-setup`
|
||
only. Switched the test's transcript writes to `write_bytes()` for
|
||
deterministic LF; production `_tail_transcript_bytes` was already CRLF-safe.
|
||
|
||
## [2026-07-01]
|
||
|
||
### Added
|
||
|
||
- **`aipass init` is now a template selector (TDPLAN-0010)** — `init` presents a
|
||
chooser with **`empty project`** at the top, pre-selected as the default
|
||
(creates just the project folder, no scaffold), and **`aipass_framework`**
|
||
below it (the full AIPass agent framework — the old always-on behavior, now
|
||
opt-in). Flag and positional forms both work: `aipass init --list` (branches
|
||
before the `--` catch-all) and `aipass init <template>`. The AIPass-specific
|
||
stages (8 spawn-first-agent / 9 ping-registry / 11 handoff / 12 init_report,
|
||
`AIPASS_SPECIFIC_STAGES`) and the `bootstrap.init_project()` scaffold are now
|
||
gated on the chosen template, so an empty project stays empty. In-product pip
|
||
hints in `init_flow.py` + `doctor.py` retuned to clone/`setup.sh`. 8 new
|
||
selector tests; 499 tests pass. (built by @aipass, FPLAN-0295, TDPLAN-0010)
|
||
|
||
- **Unified Telegram ↔ Claude Code bridge — CC-native session discovery
|
||
(DPLAN-0226)** — a Telegram message to a branch's bot now lands directly in
|
||
that branch's live Claude Code session, and the reply tails back out to
|
||
Telegram — a full round trip, **live-proven end-to-end from Patrick's own
|
||
Telegram client** (not just a self-test). The bot's inbound path
|
||
(`base_bot.ensure_tmux_session`) discovers the active session by enumerating
|
||
CC-native `~/.claude/sessions/<pid>.json` files (match `cwd`, confirm PID
|
||
alive, newest by `startedAt`), maps it to a tmux pane by cwd, and injects the
|
||
message — replacing the old `PRESENCE.central.json` pointer, which is kept but
|
||
commented out. The outbound path gains a CC-native "Strategy 0" in
|
||
`_resolve_active_transcript` that prefers the discovered transcript, so
|
||
assistant replies relay back reliably. Anthropic ToS rules out a cloud peer,
|
||
so all delivery is local (tmux/PTY). New `session_boot.py` boot wrapper
|
||
(attach-if-live-else-start-in-tmux; a thin `~/.bashrc claude()` shim delegates
|
||
to it). Hooks tests 66 green (presence_gate / cc_sessions / session_boot),
|
||
telegram presence_pointer 42 green. (DPLAN-0226 P1/P2, FPLAN-0290/0291/0292)
|
||
|
||
- **Seedgo stale-template audit checker (`template_check`)** — a new advisory
|
||
standard that flags branches still carrying unrendered template markers in
|
||
their local prompts / config, so a citizen that never customized its scaffold
|
||
no longer fails silently. Auto-discovered like every other checker; advisory
|
||
(warns, never blocks). Ships with `template_content.py` and a `template.md`
|
||
standard doc, covered by `test_template_check.py`. (built by @seedgo, DPLAN-0228)
|
||
|
||
### Fixed
|
||
|
||
- **Drone `--json` output no longer corrupts machine JSON** — `--json`
|
||
pass-through was routed through Rich's `console.print()`, which defaults to
|
||
width 80 on a non-TTY and hard-wraps mid-string, producing invalid JSON
|
||
(e.g. `"Security \nScan"`). Fixed by writing raw JSON with `sys.stdout.write()`
|
||
in the pass-through paths (`drone.py` + `router.py`) while keeping Rich for
|
||
drone's own human UI. Verified live end-to-end. (fixed by @drone, td-49)
|
||
|
||
### Changed
|
||
|
||
- **README: pip removed, clone-only install (TDPLAN-0010)** — the top-level
|
||
README no longer documents `pip install aipass` anywhere: the PyPI badge, the
|
||
install steps (hero + Quick Start), the Project Status version badge, and the
|
||
uninstall `pip uninstall` line are all removed. Install is now a single path —
|
||
`git clone … && ./setup.sh` (puts `aipass` + `drone` on PATH), then
|
||
`aipass init` scaffolds agents into your own project on top. Quick Start
|
||
reorganized into Install → Your own project → Explore the full framework.
|
||
(packaging code untouched; docs are clone-first.) (DPLAN-0228, devpulse)
|
||
|
||
- **Spawn: `builder` template → `aipass_framework`, birthright retired,
|
||
per-project registry targeting (TDPLAN-0010)** — the citizen_class/template
|
||
`builder` is renamed to **`aipass_framework`** across `class_registry.py`,
|
||
`core.py`, `meta_ops.py`, `update_ops.py`, `sync_registry_ops.py`, help text,
|
||
and the template dir itself (`templates/builder/` → `templates/aipass_framework/`).
|
||
The class is no longer baked as a literal in the template passport — a new
|
||
**`{{CITIZEN_CLASS}}` placeholder** (passport line 21, `placeholders.py`) now
|
||
takes it from the create call. **`birthright`** (0 live users) is retired to
|
||
`templates/.archive/birthright/` and its `passport` command disabled
|
||
(`passport.py` / `passport_ops.py` → `(disabled).py`, routing removed).
|
||
**Per-project registry targeting:** `spawn`'s `find_registry()` no longer
|
||
passes `package_root` to the shared discovery (killing the silent fallback to
|
||
AIPass's own registry for external targets), and `_spawn_agent` now validates
|
||
containment and, if the found registry is outside the target's project, walks
|
||
up from the target for `.git`/`pyproject.toml`/`setup.py`/`setup.cfg` to use
|
||
**that project's own registry** — so an agent created into any project is
|
||
tracked by that project's registry, never AIPass's. The
|
||
`_validate_path_containment` isolation invariant is untouched. `create` also
|
||
degrades gracefully when `@memory` is unavailable (empty meta-tabs, no crash).
|
||
297 tests pass. (built by @spawn, FPLAN-0294, TDPLAN-0010)
|
||
|
||
- **Drone resolution + access checks made project-portable (TDPLAN-0010
|
||
foundation)** — five `src/aipass`/fixed-depth self-location hardcodes are
|
||
replaced with `.trinity/`-marker walk-ups: `rm_handler` sibling protection,
|
||
`commit_handler` test-gate branch detection, `broker/daemon` allowed-bases,
|
||
the `handlers/__init__` import-guard access check (now `is_relative_to()`
|
||
instead of scanning path parts for the literal `aipass`), and
|
||
`registry_handler`'s `parents[4]` last-resort (now a
|
||
`.git`/`pyproject.toml`/`setup.py`/`setup.cfg` marker walk). `@name`→path
|
||
resolution now works for an agent in any project layout via a CWD-first
|
||
registry walk (AIPASS_HOME only as a last resort when the CWD ancestry has no
|
||
registry at all). The `_validate_branch_path` containment invariant is
|
||
untouched — per-project isolation preserved. (Drone uses its own resolver, not
|
||
the shared `registry_discovery.py`.) 838 tests pass. (built by @drone,
|
||
FPLAN-0296, TDPLAN-0010)
|
||
|
||
- **ai_mail routing made project-portable (TDPLAN-0010 foundation)** — the
|
||
fixed-depth `_REPO_ROOT = parents[2].parents[2]` self-location in
|
||
`email.py` / `email_send.py` / `dispatch.py` (4 sites) is replaced with the
|
||
portable `find_repo_root()` marker-walk already used in
|
||
`delivery.py` / `wake.py` / `paths.py`, so mail resolves via the project
|
||
marker instead of a hardcoded tree depth — a prerequisite for agents that
|
||
live outside `src/aipass/`. Per-project isolation preserved (no cross-project
|
||
mailbox routing). 737 tests + seedgo 100%. (built by @ai_mail, FPLAN-0293,
|
||
TDPLAN-0010)
|
||
|
||
- **Presence gate re-sourced to CC-native session files (presence_gate v2)** —
|
||
the single-session guard now sources truth from `~/.claude/sessions/<pid>.json`
|
||
via a new `cc_sessions` module (`find_occupant`/`find_live_for_cwd`) instead of
|
||
`PRESENCE.central.json`. Resume-aware (a `/resume` keeps the same PID, so the
|
||
session is correctly recognized as re-entry, not a duplicate) and exit-aware
|
||
(CC deletes the file on clean exit). `handle_stop` is now a plain no-op —
|
||
cleanup is CC's job. The old `presence.py` / `PRESENCE.central.json` are
|
||
preserved, just no longer sourced. (DPLAN-0226 P1)
|
||
|
||
## [2026-06-25]
|
||
|
||
### Added
|
||
|
||
- **Daemon auto-runner — systemd user timer (the deferred last mile of the
|
||
decentralized scheduler)** — `.daemon/schedule.json` jobs now fire **hands-off**.
|
||
A oneshot `daemon-tick.service` + `daemon-tick.timer` (every ~2 min, mirroring
|
||
the `prax-monitor.service` pattern: user-scope `~/.config/systemd/user/`, `%h`
|
||
not hardcoded paths, venv-python ExecStart `-m aipass.daemon.apps.daemon run`,
|
||
logs to `~/.aipass/daemon-tick.log` outside any tailed dir) reuses the existing
|
||
fcntl-locked `run.py` tick unchanged — the timer is the ticker. New
|
||
`apps/modules/timer_install.py` installs/enables it idempotently. Live-proven:
|
||
@devpulse received a `DAEMON TEST` ping from a branch woken purely by the timer,
|
||
no human tick. Tick profile: ~1.7s (import overhead only); the earlier CPU spike
|
||
was `wake_branch` spawning opus agents concurrently, **not** the tick — so
|
||
scheduled wakes want light models + staggering. Closes the piece DPLAN-0204 /
|
||
FPLAN-0282 deferred. 461 daemon tests green, seedgo 100%. (FPLAN-0287)
|
||
|
||
- **Prax monitor → Telegram relay (`prax_monitor` bot)** — the live
|
||
`drone @prax monitor run` Mission-Control feed now mirrors to a dedicated
|
||
Telegram bot, so the whole-system monitor is watchable from a phone ("same
|
||
monitor, different window"). New `monitoring/telegram_relay.py` taps the single
|
||
render seam (`_render_event`), buffers events, and flushes every 5s (4000-char
|
||
split, 150-line flood cap, `disable_notification`); fail-silent-once when
|
||
unconfigured. Gated behind `--relay` / `AIPASS_PRAX_MONITOR_RELAY=1` so a local
|
||
`monitor run` stays console-only (no double-send). Bot config (token + chat_id)
|
||
loads from the @api secret `telegram/prax_monitor`. Ships a reboot-survivable
|
||
`prax-monitor.service` user unit. 937 prax tests green (31 new). (DPLAN-0221)
|
||
|
||
- **Self-documenting `.trinity` state-tabs** — each memory-file section
|
||
(`todos` / `key_learnings` / `sessions` / `observations`) now carries a
|
||
config-sourced `⟦ rollover ON/OFF · keep N · ≤chars ⟧` tab rendered directly
|
||
above it, so an agent editing a section sees its rollover state and character
|
||
cap at the edit point (stops over-limit writes). Values are generated from
|
||
`memory.config.json` (single source of truth) via @memory's new
|
||
`render_all_meta_tabs()` / `tab_renderer.py`; @memory's `spawn_pusher` carries
|
||
the `{{*_META}}` placeholders into @spawn's branch templates, and @spawn
|
||
resolves them at create (`build_replacements_dict`, fail-loud on missing keys)
|
||
so new branches auto-populate. `refresh_all_tabs` keeps live branches synced;
|
||
@memory README documents the system. (FPLAN-0285, FPLAN-0286)
|
||
|
||
### Changed
|
||
|
||
- **Todo management — delete-on-done discipline** — `todos[]` are operational
|
||
and exempt from rollover (confirmed; the vestigial `todos` entry was removed
|
||
from `memory.config.json` rollover defaults). Because rollover never trims
|
||
them, finished todos must be **deleted**, not left as `status: done` (which
|
||
pile up and resurface as "open" across sessions). `/prep` and `/memo` (Claude
|
||
+ Codex) and the `CLAUDE.md` startup protocol now codify: delete each todo when
|
||
done (proof → session entry), reconcile on load. (FPLAN-0285)
|
||
|
||
### Fixed
|
||
|
||
- **Daemonized wakes killed by systemd cgroup teardown (td-48)** — timer-fired
|
||
`wake_branch()` calls spawned the dispatch monitor + claude child, then died
|
||
within seconds with no email and a stale lock, while the *same* wake from an
|
||
interactive terminal worked. Root cause: a systemd oneshot service defaults to
|
||
`KillMode=control-group`, so when the ~1.7s tick process exits, systemd SIGTERMs
|
||
**every member of its cgroup** — `start_new_session=True` is irrelevant because
|
||
systemd tracks by cgroup, not process group. Fix in `ai_mail` dispatch: detect
|
||
the systemd context (`INVOCATION_ID`) and re-spawn the monitor via
|
||
`systemd-run --user` in its **own transient unit**, escaping the parent cgroup
|
||
(falls back to direct `Popen` when not under systemd); plus `stdin=DEVNULL` on
|
||
both the monitor and claude `Popen` calls and monitor PID self-registration in
|
||
the lock. Now genuinely live-proven through the timer: 3 branches
|
||
(commons/cli/backup) woken purely by `daemon-tick.timer` each emailed @devpulse
|
||
and exited clean (~20s, code=0). 737 ai_mail tests green, seedgo 100%.
|
||
|
||
- **seedgo-audit — telegram ported-but-unwired functions** — the DPLAN-0218
|
||
relocation pulled the telegram lib into the seedgo gate's scope, surfacing 16
|
||
`unused_function` flags across 8 handler files. These are *not* dead code —
|
||
they're ported-but-unwired from the ~9k-line Dev-Pass port (S249), awaiting
|
||
DPLAN-0220 wiring (on_response hooks, response_router, tmux session mgmt, file
|
||
up/download, multi-bot, config helpers). Added name-scoped `unused_function`
|
||
bypasses in `skills/.seedgo/bypass.json` (the existing mechanism), each citing
|
||
DPLAN-0220, and documented every one in `SKILL.md` → *Ported-but-unwired* with
|
||
a "remove the bypass as you wire each fn" note. @skills back to 100%.
|
||
|
||
- **seedgo-audit — @spawn direct JSON read** — `core.py` adopt-path read a
|
||
passport via `json.loads(path.read_text())` (direct file op), failing the
|
||
`json_handler` standard and the CI seedgo-audit gate. Switched to
|
||
`json_handler.read_json()` (the same pattern used a few lines above), dropping
|
||
the now-unused `import json as _json`. @spawn back to 100%; 315 spawn tests
|
||
green.
|
||
|
||
- **Windows CI — telegram `bot_registry` crashed test collection** — the module
|
||
did a bare `import fcntl` (POSIX-only), so on Windows all 8 telegram test
|
||
modules that transitively import it failed at *collection* with
|
||
`ModuleNotFoundError: No module named 'fcntl'`, reddening Windows Test on the
|
||
last several PRs. Guarded the import (`try/except ImportError → fcntl = None`,
|
||
the established hooks/daemon convention) and routed the three flock call-sites
|
||
through no-op-on-Windows `_lock`/`_unlock` helpers — advisory locking still
|
||
applies on POSIX, is skipped where unavailable. Fixing collection then
|
||
*unmasked* three telegram tests that had never actually run on Windows, all
|
||
test-portability bugs (not product bugs): a log-streamer byte-count broke on
|
||
CRLF translation (fixture now writes `newline=""`); a registry write-failure
|
||
test used the Unix-only `/proc` path (now a cross-platform file-as-directory
|
||
parent); and `validate_bot_config` rejected valid POSIX `work_dir`s on Windows
|
||
because `Path.is_absolute()` is host-dependent (now tests POSIX *and* Windows
|
||
absoluteness). 493 telegram tests green.
|
||
|
||
- **prax-monitor service feedback loop** — the unit wrote its own stdout into
|
||
`system_logs/`, the very directory the monitor tails *and* @trigger watches,
|
||
creating a self-reinforcing loop (monitor output → re-tailed and recorded by
|
||
@trigger into `trigger_data.json` → reported as a file change → more output).
|
||
Moved the service log to `~/.aipass/` to break the cycle. Also corrected the
|
||
ExecStart to `monitor run` (relay enabled via env) — the module `__main__`
|
||
rejects the drone-style `run all --relay` argument form. (DPLAN-0221)
|
||
|
||
## [2026-06-24]
|
||
|
||
### Changed
|
||
|
||
- **Skill library relocated to `src/aipass/skills/lib/`** — first-party skills
|
||
were split across `catalog/` (built-in, cross-branch) and `.aipass/skills/`
|
||
(the branch-prompt dir, cwd-relative). Renamed `catalog/`→`lib/`, moved the
|
||
telegram skill in, archived three orphan test-fixture skills, and retired
|
||
`.aipass/skills/` from the branch. This unifies all 6 first-party skills under
|
||
one built-in tier and **fixes the telegram skill not being discoverable from
|
||
other branches** (it sat in a cwd-relative path). The public discovery
|
||
convention (`.aipass/skills/` + `~/.aipass/skills/`) is unchanged. One
|
||
functional line changed (`discovery_handler` built-in path); telegram's test
|
||
`conftest` path-depth, the systemd `.service` ExecStart, and seedgo bypass +
|
||
test paths were updated to match. Packaging, imports, and gitignore are
|
||
unaffected (everything stays under `src/aipass/`). 252/252 skills tests green;
|
||
cross-branch discovery verified from another branch. Moving telegram into the
|
||
gate's scope newly surfaced 9 pre-existing `unused_function` flags in its
|
||
handlers — triage tracked separately. (DPLAN-0218)
|
||
|
||
### Added
|
||
|
||
- **`@api` in-process `set_secret` write-door** — `aipass.api.apps.modules.secrets.set_secret(provider, slug, value, *, as_json=False)`
|
||
mirrors the existing `get_secret`, writing `~/.secrets/aipass/<provider>/<slug>.json`
|
||
(dirs `0o700`, files `0o600`, value never echoed to stdout or logged). The @api
|
||
secrets store was previously read-only; this is the writer the telegram
|
||
mother-bot needs to persist a newly-created bot's config so the child can read
|
||
its token. 515 @api tests pass (11 new), @api seedgo 100%. (DPLAN-0220)
|
||
|
||
- **Prax-monitor v1 on Telegram — `/monitor` system-wide log subscription** —
|
||
the old Dev-Pass "prax monitor bot" (a `@prax` push relay on a dedicated token)
|
||
was stripped during the port; this revives the capability as a feature of the
|
||
existing `@aipass` bot (no second bot, no new credential). New `/monitor on`
|
||
(errors+warnings) / `all` (firehose) / `off` / `status` command on `base_bot`,
|
||
shown in the slash menu + `/help`. The subscribed chat is persisted to the `@api`
|
||
store (`set_secret('telegram','monitor',{chat_id,mode})`) so it survives restart,
|
||
and `base_bot` boot-starts the stream from it on startup — set-and-forget under
|
||
systemd. `LogStreamer` gained `system_wide` (glob all `system_logs/*.log`, not one
|
||
branch) + `level_filter` (default keeps `WARNING`/`ERROR`/`CRITICAL`, `all` =
|
||
passthrough); `_init_positions` still seeks EOF so subscribing never floods
|
||
history. 33 new tests (`test_monitor.py`), telegram suite 493/493, skills 252/252,
|
||
@skills seedgo 98%. (First @skills run crashed mid-edit on 3 string-handling
|
||
syntax errors; continued + fixed.) The richer AS-WAS `@prax` event-feed relay
|
||
(rendered Mission-Control stream, needs a dedicated-bot-token decision) is tracked
|
||
as Route B. (DPLAN-0221)
|
||
|
||
### Fixed
|
||
|
||
- **Telegram port — wave 1 (persistence + monitor + state hygiene)**, surfaced by
|
||
a full completeness audit against `TELEGRAM_PORT_MAP.md` (366 tags, ~83% ported,
|
||
452/452 tests green): (1) **bot launch** — `bot_factory.start_bot_process` and
|
||
`telegram-bot@.service` used a non-existent `~/.venv/bin/python3`; now launch via
|
||
`sys.executable -m …base_bot` (added `lib/__init__.py` + `lib/telegram/__init__.py`
|
||
for package resolution, since base_bot uses relative imports). (2) **reboot
|
||
survival** — `enable_service` now installs the systemd unit to
|
||
`~/.config/systemd/user/` + `daemon-reload` (previously the unit was never
|
||
installed, so `enable` silently no-op'd). (3) **state hygiene** — gitignored
|
||
`skills/.../lib/telegram/.local/` so the runtime registry/offset/lock files stop
|
||
leaking into the repo. (4) **prax-monitor** — `log_streamer` tailed a hardcoded
|
||
`~/system_logs` while prax writes to the repo-root `system_logs`; now resolves the
|
||
repo root (honoring `AIPASS_TEST_LOG_DIR`) so the log stream actually delivers.
|
||
(5) **auto-create (GAP1)** — `create_bot` wrote a new bot's config only to a disk
|
||
shadow file while the runtime loads its token exclusively from the @api store, so
|
||
a minted bot started then exited with no config; `create_bot` now calls
|
||
`set_secret('telegram', bot_id, config, as_json=True)` (fail-loud) so the
|
||
create→@api→load round-trip works and the mother-bot can mint startable bots. New
|
||
round-trip + fail-loud tests; telegram suite 454/454.
|
||
(6) **/help + Telegram command menu** — `setMyCommands` only ran inside
|
||
`create_bot`, so hand-launched bots (like the live `@aipass`) had no slash-menu,
|
||
and the menu list had drifted from `/help`; `base_bot` now sets its menu on
|
||
startup from a single source (`build_botfather_commands`, also used by
|
||
`create_bot` — `DEFAULT_BOT_COMMANDS` retired), so the Telegram menu and `/help`
|
||
list the same enriched commands incl. `/create`/`/cancel`. Wiring the builder
|
||
(rather than deleting it as "dead") also lifted Unused_Function 92→93%. 6 new
|
||
tests, telegram suite 460/460. (A running bot needs a restart to pick up the
|
||
startup menu.)
|
||
(DPLAN-0220)
|
||
|
||
- **Telegram `@aipass` deployed under systemd (reboot survival + clean lifecycle)** —
|
||
the live mother-bot was a hand-launched foreground process: no reboot survival, and
|
||
`stop_bot`/restart targeted an uninstalled `telegram-bot@base` unit, so there was no
|
||
working lifecycle command. Installed the user service + `enable --now` +
|
||
`loginctl enable-linger` (`Linger=yes`); the 17:26 startup log confirms the full
|
||
chain live — `Telegram API OK`, **`Command menu set (6 commands)`** (the new `/help`
|
||
menu), stale-lock cleanup, poll loop, tmux Claude session preserved, `NRestarts=0`.
|
||
Also corrected the ported unit's `StandardOutput`/`StandardError`, which pointed at a
|
||
non-existent `~/system_logs` (would have crash-looped the service) — now
|
||
`<repo>/system_logs`, matching where the app already logs. Restart is now
|
||
`systemctl --user restart telegram-bot@base`. (DPLAN-0220)
|
||
|
||
- **seedgo CLI help checkers green-lit non-compliant `--help` output** — the
|
||
`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's plain text through the approved console API and dodging
|
||
the existing `parser.print_help()` ban. Closed the loophole: `cli_check` now
|
||
flags `.format_help()`, `cli.md`/`cli_content.py` name it alongside
|
||
`print_help()`, +2 regression tests. Also rewrote `@ai_mail`'s `print_help()`
|
||
to render hand-rolled Rich (the `--help` content was complete, just unstyled).
|
||
A behavioral `--help` check (run it, assert not raw argparse) is noted as a
|
||
follow-up. (DPLAN-0217) On its first CI run the tightened checker immediately
|
||
surfaced the same pattern in 4 `@api` modules (`api_key`, `usage_tracker`,
|
||
`google_client`, `openrouter_client`) — migrated to Rich, `@api` back to 100%.
|
||
- **seedgo `readme_check` ignored the `(disabled)` marker in self-scans** — its
|
||
module-list and test-count scans now skip `foo(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_function` bypasses 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). `lines` stays 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 to `CLOSE 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 `.backupignore` default moved out of code into a template file** — the
|
||
seed content backup writes into a new project's `.backupignore` now lives in
|
||
`backup/templates/backupignore.template` (loaded at register), matching the
|
||
AIPass convention that templates are data files, not hardcoded Python. Retired
|
||
the `BUILTIN_IGNORES` list; `_build_backupignore()` reads the template and
|
||
**raises** if it's missing — never silently empty, since an empty
|
||
`.backupignore` would back up everything and crash. Docs/comments repointed to
|
||
the template (td-30).
|
||
|
||
### Removed
|
||
|
||
- **Dead `bulletin_created` trigger handler** — the event handler that wrote a
|
||
`bulletin_board` section into every branch dashboard is retired: nothing fired
|
||
the event, its `BULLETINS.central.json` store no longer exists, and prax
|
||
already prunes `bulletin_board` as 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 `.backupignore` had 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.py` names
|
||
were fixed. Root README lists @backup and documents `.backupignore`; the navmap
|
||
was corrected. The shipped root `/.backupignore` was realigned to
|
||
`BUILTIN_IGNORES` (dropped stale `.backup_system/` + over-broad `*logs`).
|
||
@memory and @flow READMEs now cross-reference their `.backup/` writes, and the
|
||
orphaned `prax/.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").
|
||
`@daemon` joins Quality & operations; a new "Capabilities and community" group
|
||
covers `@skills` + `@commons` (td-28).
|
||
- **`/prep` now reconciles todos against reality** — the session-wrap command
|
||
(both the Claude `.claude/commands/prep.md` and 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_IGNORES` is the
|
||
**seed** that generates a new project's `.backupignore` at register and is
|
||
never consulted at backup time; `.backupignore` (via `load_spec`) is the
|
||
**runtime source of truth**. There's no static fallback, so the seed is
|
||
safety-critical — an empty `.backupignore` backs up everything and can crash
|
||
the machine. Added a "How Ignores Work" README section + code comments on
|
||
`BUILTIN_IGNORES` and `load_spec`. Also added `logs/` to the seed so new
|
||
projects exclude log directories (e.g. prax `.jsonl` output) by default, not
|
||
just `*.log` files (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 + `/compass`
|
||
human-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_PATH` standard (#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).** `@memory` `symbolic.py` builds its
|
||
8 dash-encoded branch-path names at runtime (was a literal `-home-patrick-`);
|
||
`@prax` `branch_detector.py` docstrings genericized. Both back to 100%
|
||
`Hardcoded_Path`.
|
||
- Green-CI fixes across Linux / Windows / macOS; `dispatch_monitor` PID-`429`
|
||
substring bug; git post-merge friction (FF-only realign).
|
||
|
||
## [2026-06-19]
|
||
|
||
### Fixed
|
||
|
||
- **`aipass init` now 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.json` mirrors the live wiring
|
||
(`tier0_kernel` + `navmap` enabled, `global_prompt` disabled) and `bootstrap.py`
|
||
seeds both tier `.md` files. `init update` backfills 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 `.trinity` file metadata, but
|
||
DPLAN-0210 had moved limits into @memory's `memory.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 to
|
||
`drone @memory rollover check/run`; `compact.py` reads the current list schema
|
||
(it was calling `.keys()` on a now-list `key_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_prompt` is now gone, not just disabled:
|
||
`global_loader.py` + its tests deleted, the `global_prompt` block stripped from
|
||
`.aipass/hooks.json` + `project_hooks.json`, `_resolve_global_prompt` + all global
|
||
seeding removed from `aipass init` bootstrap/update, the cadence default + bypass
|
||
entries cleaned, and both `aipass_global_prompt.md` / `project_global_prompt.md`
|
||
archived. Claude (cadence) and Codex (SessionStart) now read the same tier files —
|
||
one prompt source, every runtime.
|
||
|
||
### Added
|
||
|
||
- **seedgo `HARDCODED_PATH` standard (#37).** A new checker (`hardcoded_path_check.py`
|
||
+ `hardcoded_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, the `drone @agent --help` reflex, 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 old `global_prompt` loader
|
||
is retired (kept as a reference snapshot). Net: more navigation context
|
||
reaches agents while less is paid per turn. Fresh-clone wiring is seeded from
|
||
`cadence.py` defaults + `setup.sh` + `provider_manifest.json`.
|
||
- **Public source genericized — `Patrick` → generic `user`.** No personal
|
||
identifiers in tracked code/docs: the compass decision-source enum
|
||
(`patrick` → `user`) + the `/compass` command, the devpulse local prompt, the
|
||
`aipass init` onboarding 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_private`
|
||
personal bot from the skill's tests; the message sender now defaults to the
|
||
Telegram user's first name (fallback `User`) instead of a hardcoded `Patrick`.
|
||
|
||
### Added
|
||
|
||
- **Prompt-craft conventions harvested from Claude Code's own prompts
|
||
(DPLAN-0213).** A `Writing voice` section in `.aipass/PROMPT_STYLE.md`
|
||
(`file_path:line` refs, 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_use` field with trigger
|
||
phrases (surfaced during discovery scans) and per-step "Done when:" success
|
||
criteria across the SKILL.md templates.
|
||
- **`HARDCODED_PATH` standard (@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_change` flow 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 the `aipass init` seeding gap this surfaced.
|
||
|
||
## [2026-06-16]
|
||
|
||
### Security
|
||
|
||
- **Secrets door hardened — no raw secret value ever reaches stdout
|
||
(DPLAN-0211).** `@api get-secret` previously 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 a
|
||
`0600`-mode file via `--out FILE` (printing just the path), and `--list`
|
||
prints slug **names** only. The `telegram` skill — the sole consumer — was
|
||
rewired from subprocess-parsing `get-secret` stdout to the **in-process
|
||
secrets module API**. Clears CodeQL clear-text-logging alerts #86/#87/#88.
|
||
|
||
### Fixed
|
||
|
||
- **`@ai_mail` dispatch monitor mislabeled failures as "API rate limit" on a
|
||
PID-`429` collision.** 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 flaking
|
||
`test_sandbox_failure_sends_bounce` deterministically-by-PID in CI. The scan
|
||
now excludes the monitor's own `--- ` framing lines; genuine `429`/`529`
|
||
markers 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 `telegram`
|
||
skill 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 `@hooks` engine. Three phases — **P1 `@api`** adds
|
||
`get-secret <provider/slug> [--json|--list]` + `auth/secrets.py` (reads
|
||
`~/.secrets/aipass/`); **P2 `@skills`** ports the 14-file bridge (~5,300 lines)
|
||
+ ~424 tests into `.aipass/skills/telegram/`, rewiring every seam to services;
|
||
**P3 `@hooks`** ports `telegram_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.
|
||
|
||
## [2026-06-13]
|
||
|
||
### Changed
|
||
|
||
- **Unified memory entry schema — Phase 1 (DPLAN-0207).** All four `.trinity`
|
||
entry types (`key_learnings`, `sessions`, `todos`, `observations`) move to one
|
||
shape: numbered + dated, list-shaped, newest-first. `key_learnings` converts
|
||
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 on `number` — 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** to
|
||
`schema_version` 3.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 live `rollover check` caught 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`+`/prep` and @spawn template updates.
|
||
|
||
- **Memory config relocated to the json-home and unified behind one
|
||
self-healing loader (FPLAN-0271).** `memory.config.json` moved from the loose
|
||
tracked `config/` dir into the gitignored `memory_json/custom_config/`
|
||
(operator-tunable, fast-access) and `.plans_processed.json` into
|
||
`memory_json/` root; the empty `config/` 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 flipped `entry_limits.enforce` off, plus rollover defaulting to 600
|
||
vs the configured 500). All 9 now read through one
|
||
`apps/handlers/json/config_loader.py` with a single `DEFAULT_CONFIG` +
|
||
non-mutating deep-merge + self-heal: a missing file is rewritten from code
|
||
defaults (warn-first `enforce: false`), while malformed JSON fails loud and is
|
||
never overwritten. Dead `intake` section deleted; a static `_meta` block in
|
||
`DEFAULT_CONFIG` documents each section's consumer files. Code-as-Template:
|
||
the on-disk file is local tuning, code carries the committed defaults — same
|
||
model as hooks `cadence_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 (codify `custom_config/` as a
|
||
seedgo standard).
|
||
|
||
## [2026-06-12]
|
||
|
||
### Changed
|
||
|
||
- **Devpulse dashboard slimmed — todos no longer duplicated (startup-context
|
||
fix).** `DASHBOARD.local.json` was embedding the full `todos[]` bodies that
|
||
already live in `.trinity/local.json`; since both files are read at every
|
||
startup, that was pure duplication. The dashboard now emits `todo_count` only
|
||
(the glance value) — the bodies are commented out in the prax
|
||
`devpulse_dashboard` plugin's `todo_section.py` (revivable). Dashboard
|
||
`DASHBOARD.local.json` 6.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's `DEPRECATED_SECTIONS`:
|
||
`devpulse`, `commons_activity`, `agent_status`, `memory_bank`) were listed as
|
||
deprecated but only excluded from template *pushes* — they lingered in every
|
||
branch's live `DASHBOARD.local.json`. Added `_prune_deprecated_sections()` to
|
||
the prax dashboard `refresh` path (reusing the single `DEPRECATED_SECTIONS`
|
||
constant), so a refresh strips them. Verified: `bulletin_board` removed from
|
||
the devpulse dashboard; 116/116 prax tests, seedgo 100%. (Follow-up: `@trigger`
|
||
still has a `bulletin_created` writer 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 keys `id`/`d`/`sum` vs the actual `session`/`date`/`summary`, so it
|
||
always wrote empty strings — and it duplicated `local.json`, which loads at
|
||
startup), `todo` (carried only `todo_count`, already in `quick_status`; now
|
||
sourced directly from `local.json`), and `ai_mail` (its counts live in
|
||
`quick_status`; the section is removed from output *after* quick_status is
|
||
computed from it). End state: 4 sections (`flow`, `memory`, `git`, `dispatch`)
|
||
+ the `quick_status` glance. `session_section.py`/`todo_section.py` archived
|
||
(not deleted). `DASHBOARD.local.json` overall 6.8 KB → 2.4 KB. Verified: seedgo
|
||
100%, 108 prax tests. (Follow-up: `@ai_mail`'s `dashboard_sync.py` section
|
||
writer to retire separately.)
|
||
- **quick_status now self-sources mail counts from `inbox.json`.** Decouples the
|
||
glance from the `ai_mail` section: prax's three quick_status calculators read
|
||
`.ai_mail.local/inbox.json` directly (`_read_mail_counts`) for `new_mail`/
|
||
`opened_mail`, so the `ai_mail` section 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 — removed
|
||
`push_dashboard_update` from 5 call sites and archived `dashboard_sync.py`.
|
||
With prax self-sourcing mail counts, the `ai_mail` section now stays gone (a
|
||
mail op no longer re-adds it — verified). 737 ai_mail tests.
|
||
- **`.backupignore` is now a true `.gitignore` for the backup system — a single
|
||
source of truth (FPLAN-0269).** Replaced the hand-rolled `fnmatch`+part-loop
|
||
matcher (which broke leading-slash anchoring, `*`-crossing-`/`, dir-only `foo/`,
|
||
`!` negation, and last-match-wins) with the `pathspec` gitwildmatch library, so
|
||
`.backupignore` honors full gitignore semantics: include-by-default, `!`
|
||
negation, `#` comments, anchoring, dir-only, last-match-wins. `BUILTIN_IGNORES`
|
||
is demoted to a seed-only default (written when the file is absent, never merged
|
||
at runtime), and the separate `IGNORE_EXCEPTIONS`/`is_exception` layer is
|
||
removed (exceptions are native `!` lines). Snapshot, versioned, `all`, and
|
||
mirror-cleanup now all obey the one file. `.ruff_cache/` + `.coverage` added 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/`, dead `versions/`
|
||
removed (FPLAN-0269 follow-up).** The backup root is now `.backup/` (shorter,
|
||
coexists with `@flow`'s `.backup/processed_plans/`); the orphaned per-timestamp
|
||
`versions/` scaffold and the unused `build_versioned_path()` — both superseded
|
||
by the Phase-3 `versioned/` baseline+diff store — are gone. Drive sync confirmed
|
||
reading `.backup/versioned/` + `.backup/drive_tracker.json` via the shared
|
||
`backup_root()`. Verified by artifact (seedgo 100%, 220 tests) + live (a
|
||
throwaway project writes to `.backup/`, no `versions/` 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.py` that
|
||
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 `GoogleDriveSync` against the
|
||
live `@api` gateway (`get_drive_service` + `api_call_with_retry` — never the
|
||
console-OAuth path). New `handlers/drive/`: `DriveClient` (folder hierarchy
|
||
`AIPass Backups/<project>/`, thread-safe cache, retry-with-rebuild),
|
||
`upload.py` (resumable `MediaFileUpload`, 3 threaded workers), `tracker.py`
|
||
(mtime+size dedup → no re-upload of unchanged files), `test.py` (connectivity).
|
||
All four `drive_*` modules un-stubbed; `all` now runs snapshot→versioned→
|
||
drive-sync and **fails honestly** if Drive creds are absent (never silent-skips,
|
||
never fakes success, snapshot+versioned still report). New `restore` command
|
||
(`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, restore `list`/`file` round-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 PyQt5 `ui/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>.diff` unified-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; `all` shares 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
|
||
shared `timestamps.json`. New `diff/restore.py` (`list_versions` +
|
||
`restore_file`); `diff/generator.py` wired (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.py` `cleanup_deleted_files` — exception-aware
|
||
mirror-delete: files removed from source are now removed from the snapshot
|
||
(was a blind `rmtree`+recopy), respecting ignore-exceptions. `copy/snapshot.py`
|
||
gains mtime-skip (quick-check fast path — unchanged files no longer re-copied),
|
||
a long-path guard (>260), and read-only handling. `report/result.py`
|
||
`BackupResult` now tracks critical vs non-critical errors + warnings +
|
||
`files_deleted`; `ignore/patterns.py` gains `IGNORE_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 `backup` before the feature rebuild:
|
||
new `tests/` 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/help` guard
|
||
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.txt` added (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 `.trinity` file 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_metadata`
|
||
now honors the `skipped` flag 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 search` returns 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
|
||
single `drive_sync` run's 3 upload workers raced the folder search+create →
|
||
multiple "AIPass Backups" root folders, and `get_or_create_backup_folder` reset
|
||
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_folder`
|
||
hold `_folder_cache_lock` across the **entire** method (cache + root-ensure + search
|
||
+ create); `get_or_create_backup_folder` is 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 four
|
||
`drive_*` commands route by their underscore names (were hyphenated → "Unknown
|
||
command"); `requirements.project.txt` now 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).
|
||
|
||
- **Backup rich CLI output restored end-to-end (FPLAN-0263 + drone passthrough).**
|
||
`drone @backup snapshot|versioned|all` rendered 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
|
||
(new `backup_timestamps` state handler + `display.py` pipeline: Last-backups panel →
|
||
boxed header → live Rich progress bar → result summary → Backups-now panel;
|
||
`BackupResult` extended with `files_checked`/`files_skipped`/`backup_path`; copy
|
||
handlers emit `on_progress` callbacks). (2) drone was flattening it at the pipe —
|
||
`@backup` ran through `capture_output=True` (non-TTY → Rich strips color, the
|
||
`transient` progress bar renders to nothing) and the 30s capture timeout would kill
|
||
large backups; added `backup` to drone's `INTERACTIVE_BRANCHES` so all `@backup`
|
||
commands inherit the terminal (mirrors `cli`). Verified live under a pty: full color
|
||
+ animated progress bar.
|
||
|
||
## [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.*
|