Item 6 — aipass install pip step looked hung. setup.sh ran the heavy editable install of the [dev,memory] extras with pip --quiet, so it went SILENT for minutes during memory wheel builds (looks frozen to a first-time user). Dropped --quiet on that step so pip streams progress, and set the expectation in the echo ('can take a few minutes while the memory wheels build'). Left the fast pip-upgrade step quiet.
Item 7 — README quick-start command errored. README.md:147 showed 'drone @seedgo audit my_project', but audit takes a registered PACK name, so it fails with Unknown pack. Corrected to 'audit aipass' (matches the working example at :119).
Remaining #665 items (version hardcode, --help command names, subcommand --help contract, placeholder descriptions, bare-mode hints, crash-vs-unknown) span aipass/drone/daemon/memory/spawn and stay open for a coordinated per-owner pass. setup.sh syntax-checked (bash -n).
Rides PR#659 (issue-clearing, no main-merge).
#668 — poll loop re-drained a rate-limited backlog in a flood loop. The offset advanced AFTER process_update, so a rate-limited/rejected/erroring update never advanced it and the same backlog was re-fetched. Fix: advance the offset BEFORE process_update, so a consumed update never pins it (base_bot.py run loop).
#669 — three fixes: (1) systemd unit gets KillMode=process so a Restart is not killed by the old instance's cgroup teardown (the suicide-loop); (2) create_bot_via_botfather now RAISES RuntimeError with an actionable message (names the set-secret command) instead of silently returning None when telethon config is missing/unready — fail-honestly (botfather_client.py); (3) stale config-mechanism docstrings corrected (bot_factory/bot_operations).
Bonus (unbriefed but correct + beneficial): @skills also Windows-hardened _is_pid_alive (OpenProcess+GetExitCodeProcess on win32, os.kill moved into the POSIX branch) + refactored _check_lock to use it, and switched TEMP_DIR to tempfile.gettempdir(). Side effect: base_bot.py os.kill is now platform-guarded.
Built by @skills, verified by devpulse: 653 telegram tests green (incl lock/pid tests exercising the refactor); #668 offset-before-process verified by inspection; #669.2 raise covered by test_botfather_client. Note: @skills dispatch bounced on a usage-limit retry AFTER completing the work — verified the on-disk result independently.
Rides PR#659 (issue-clearing, no main-merge). Source: devpulse todos #41/#52.
Two hardening items surfaced during #664 that @memory could not touch (cross-branch edit gate blocked it).
ITEM 1 — _find_repo_root fail-loud (lifecycle/rollover.py). The PreCompact rollover hook's _find_repo_root() returned None SILENTLY when AIPASS_HOME/cwd was wrong -> rollover no-ops invisibly (the exact silent-skip that hid #664 for months). Now logs a logger.error with the AIPASS_HOME value + cwd before returning None (still degrades, just visibly).
ITEM 2 — edit_gate soft entry-count guard (security/edit_gate.py). edit_gate enforced per-entry CHARACTER caps but not entry COUNTS, so a branch could drift past its count cap between rollovers. New _check_section_counts warns (NEVER blocks) when a rolling section exceeds its cap, reading the SAME memory.config.json rollover caps @memory uses (config_loader.section('rollover') -> per_branch/defaults -> count); wrapped so a config-import failure degrades silently.
Built by @hooks, verified by devpulse: 70 tests green (+14 incl never-blocks guarantee, boundary cases, per-branch override, import-failure resilience); LIVE repro proves item1 logs the error on a bad root and item2 warns over-cap (20/15) without blocking; config structure confirmed to match memory's real caps (not inert).
Rides PR#659 (issue-clearing, no main-merge). Source: #664 verify (S292).
registry.is_owner (apps/handlers/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 while the lowercase form returned True. Harmless today — the only live caller (@ai_mail dispatch_monitor._wake_sender) lowercases first — but the frozen TDPLAN-0012 contract promises a normalized email, and PART-4 owner-gating of watchdog/feedback may pass a raw branch name.
Fix: lowercase BOTH sides of the comparison (passed-in email AND registry owner email), @-strip preserved. +1 case-insensitivity test. Built by @spawn, verified by devpulse: LIVE repro — every case variant of the owner (DEVPULSE/@DEVPULSE/DevPulse) resolves True, non-owners (seedgo/@SEEDGO) and empty stay False; 316 spawn tests green (+1), seedgo 100%.
Rides PR#659 (issue-clearing, no main-merge). Source: #678/TDPLAN-0012 verify.
Two rough edges on the JSONL stall detector, both hardened in one pass on my own module (apps/handlers/watchdog/agent.py).
PART 1 (false-positive): _has_jsonl_activity inferred liveness purely from JSONL file-size growth over the 120s window. An agent doing ONE genuinely long operation (big Read, long Bash, heavy compute) writes no new JSONL lines for that span -> read as idle -> STALLED fires WHILE the agent is actively working. Fix: watch_agent now also treats an in-flight tool_use as activity. While a tool runs, the assistant's tool_use is the last transcript entry; new _last_entry_is_inflight_tool() tail-reads the newest .jsonl and detects it (fully defensive -> False on any parse/shape drift, degrading to size-based). LIVE-PROVEN against real Claude Code transcripts: sampled my own session across a 10s in-flight bash -> tool_use line is written at tool START and persists the whole call (the sub-second flush lag is irrelevant at the 120s horizon).
PART 2 (invisible stall): the stall only hit _stderr()+logger. The Monitor tool that arms the watchdog turns each STDOUT line into a live event but only captures stderr to a file (never surfaced) -> devpulse never saw the stall until the 600s timeout. Fix: new _stdout_event() emits the stall (+ a long-running-tool advisory for a possibly-hung tool, + a resumed signal) to stdout so Monitor relays it live; the verbose trail stays on stderr+logger.
Stall logic extracted into a StallTracker class (kills deep-nesting). +9 tests (unit + full-loop stdout proofs + real-transcript schema check); 142 watchdog tests green, seedgo audit 100%, no type errors.
Rides PR#659 (issue-clearing campaign, no main-merge).
@spawn: owner + registry_id written into the SEALED registry entries (authority lives in registry, not the self-editable passport). ensure_project_has_owner() now keys off citizen_class=manager (was earliest-created, which mislabeled @aipass) and writes the registry entry. get_owner()/is_owner() resolvers added. 315 tests, seedgo 100%.
@hooks: new registry_gate PreToolUse handler seals *_REGISTRY.json — blocks raw writes/tee/sed/rm + Edit/Write/MultiEdit, redirects to drone @spawn; per-clause bypass defeats compound-command smuggling; reads allowed. 82 tests, seedgo 100%.
@ai_mail: wake-back reslope — SKIP_SENDERS blocklist replaced by an is_owner allowlist. Only the project owner is woken when their dispatched agent completes; all other guards intact (depth cap, lock, occupancy, honest messaging, dispatch_wake.log). seedgo 100% on the changed file.
devpulse cross-part verify (REAL unmocked resolver): is_owner resolves devpulse-only; gate 13/13 incl compound-smuggle blocked + reads/drone-@spawn allowed; wake-back wakes owner / skips non-owner / respects depth-cap; 195 new-suite tests green together. Note: AIPASS_REGISTRY.json is gitignored — this ships the CODE; owner data regenerates per-install via ensure_project_has_owner. Still open (PART 4): gate watchdog+feedback on is_owner; portability of owner-only privileges across projects.
setup.sh merges user hooks instead of overwriting (DPLAN-0234 Strand C). AIPass bridge entries are refreshed on every install (bridges/claude.py marker); user-wired hooks and custom events now survive install/re-run. Fixture-verified: customs preserved, stale bridge entries replaced without duplicates, fresh-install output shape-identical (7 events, 6+6 entries). Background: full hook fire-test on fresh Linux Docker install passed 17/17.
- README: setup.sh mention tied to the ./aipass install command + notes hook merge-not-overwrite
- CONTRIBUTING: contributor bootstrap is ./aipass install --no-init (no first-project scaffold in the engine repo)
- bash passes IS_WINDOWS into the hook-install heredoc; bridge string picks .venv/Scripts/python.exe vs .venv/bin/python3
- @hooks assessment: $AIPASS_HOME expansion fine (CC runs hooks via Git Bash on Windows), bridge has zero POSIX assumptions — interpreter path was the only gap
- Verified both OS modes + merge-marker/custom-hook regression
./aipass repo-root launcher — cold-clone entry (DPLAN-0234 Strand B, built by @aipass). git clone && cd AIPass && ./aipass install is now the whole cold-clone flow: pre-setup only 'install' works (delegates to setup.sh with flag pass-through), post-setup the launcher execs the venv binary transparently. 13 launcher tests, @aipass suite green, seedgo 100%. README Quick Start updated.
- New stdlib-only bash launcher at repo root: pre-setup only 'install' works (delegates to setup.sh, full flag pass-through); post-setup execs the venv aipass binary transparently
- 13 launcher tests (tests/test_launcher.py), bypass.json architecture entry for the test file
- README Quick Start leads with ./aipass install; CHANGELOG entry
setup.sh chains into aipass init run — clone-first one-command install (DPLAN-0234 Strand A). git clone && ./setup.sh now takes a new user from cold clone to an initialized first project in one command. --no-init/--with-init/--project mirror aipass install's handoff rules; CI + piped shells skip automatically (windows/macos-test workflows untouched, proven in clean-room Docker run 1). install.py passes --no-init so the pip path doesn't double-init. Clean-room Docker: both runs exit 0.
CI flagged @aipass at 99%: install.py had no no-args introspection gate and a direct mkdir. Both are sanctioned patterns (binary-invoked 'aipass install' bare-runs; bootstrap dir-prep before services exist) matching doctor/init_flow/profile precedent. @aipass authored the bypass entries; landing them. Local audit now 100%, pyright clean.
fix(hooks): TG replies no longer overwrite the previous message — clear processing_message_id in _advance_pending after first delivery so remote/mirror/multi-Stop turns send new messages instead of re-editing. +2 regression tests, 114/114.
The split Dependabot PRs (#init, #analyze) each bumped one path in security.yml,
leaving the sibling at v4.36.2 -> CodeQL fails 'init and analyze must match'.
Bump both to v4.36.3 (SHA 54f647b) in one commit, and add a dependabot groups
block so codeql-action (init/analyze/upload-sarif, one monorepo release) always
lands as a single grouped PR. Fixes the two red Security Scan runs.
Follow-up to 4105a7e. CI Windows Test surfaced 3 Windows-only test failures where
the sweep cross-platformed the code but tests still asserted POSIX behavior, plus
a fleet-wide architecture ripple from the template scaffold test:
- ai_mail: 3 Popen detach sites now use creationflags=CREATE_NEW_PROCESS_GROUP on
win32 / start_new_session on POSIX (real win32 detachment); test asserts the
platform-correct kwargs.
- drone: test_rm.py /tmp assertions guarded to POSIX-only (win32 uses
tempfile.gettempdir()); the swept code already dropped hardcoded /tmp on win32.
- seedgo: architecture checker exempts template scaffold test files
(test_scaffold.py via TEMPLATE_IGNORE_PATTERNS) from branch conformance -- a
template example test is not a structural requirement in every branch. Restores
all 17 branches to 100%.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uzDhtcZ6wT1T9e2AHPQig
The 3 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).
Dispatched @cli/@drone/@prax (each owns its identity); verified independently —
0 stub markers, all three Template 100%, real coherent content.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uzDhtcZ6wT1T9e2AHPQig
@aipass TDPLAN-0011 doc-drift request (repo-level doc = devpulse git territory):
1. Stage count 12->11 in rows 3.2 and 7.2 (aipass init has been 11 stages since S42;
row 3.3 already said 'all stages', no numeric drift there).
2. 'drone @hooks hookstatus' -> 'drone @hooks status' in Phase 6.3 and the per-branch
smoke matrix (hookstatus is Unknown on current drone — gap #9 itself).
3. Added a 'Machine pre-flight' note to the 3-layers intro: aipass init runs a
layer-3-lite pre-flight, and 'aipass doctor --cross-os/--e2e/--record' is the
machine sweep that augments (never replaces) the human layer-3 pass.
Left the two legit 'other 12 branches' references (branch count) untouched.
Closes devpulse todo #64.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uzDhtcZ6wT1T9e2AHPQig
Pass 3 / final. The definitive-marker scan still matched {{BRANCH}} inside markdown
inline code — spawn's README documents 'Replace `{{BRANCH}}` in...', which is
scaffolding docs, not an un-rendered stub (spawn scored 66%). For .md files, fenced
+ inline code is now stripped once up front before BOTH the definitive and
single-curly scans; passport.json (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%; spawn + seedgo cleared to 100%; only
the three genuine unconfigured prompt stubs (cli/drone/prax) still flag. +3 tests
(24/24), full suite green (1132). Completes the checker-solid work begun in 26893fb.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uzDhtcZ6wT1T9e2AHPQig
The advisory 'template' stale-checker matched marker strings anywhere in a file,
firing on documentation ABOUT templates rather than un-rendered stubs. Two root
causes fixed:
1. Scanned .trinity/*.json (all memory) — local.json/observations.json accumulate
marker mentions (seedgo's own note about the checker, prax's template_pusher
note). Now scans passport.json only, the sole spawn-templated trinity file.
2. Single-curly {…} regex ran on every .md, matching inline JSON/f-strings/code
paths in READMEs. Now single-curly detection runs on the branch prompt only
(README template has no single-curly placeholders) and strips fenced + inline
code first.
Definitive-marker detection unchanged — real stubs (cli/drone/prax prompts) still
flag. Verified live: seedgo 100%, drone/prax flag only the real prompt stub.
+4 tests (21/21). Dispatched to @seedgo (owner), verified independently.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uzDhtcZ6wT1T9e2AHPQig
New post-2.6.1 changelog period (unreleased, merge held for later). Documents the
_advance_pending processing_message_id fix under Fixed (@hooks, f42a98b, PR #651).
_advance_pending kept the pending file with a frozen processing_message_id, so
any Stop firing without a fresh placeholder (remote/mirror input or multi-Stop
turns) re-edited the same Telegram message instead of posting a new one. Clear
processing_message_id after the first delivery so subsequent Stops fall through
to send a new message. Live-proven on the devpulse bot; +2 regression tests
in TestAdvancePending (114/114).
devpulse-tier (owner) verb: fetch origin, VERSION GUARD (tag X.Y.Z must match origin/main pyproject + __init__), EXISTS GUARD (refuse if tag exists), tag origin/main + push -> fires publish.yml. 'tag --list' is global tier. Removes the last user-input step from releases (Patrick request, S274). Merge playbook (merge.md) updated to use it. Verb proven live: cut v2.6.1.
PATCH bump riding into PR#646 so main's merge commit carries the release version. pyproject + __init__ = 2.6.1 (must match the v2.6.1 tag). CHANGELOG [2026-07-02] leads with the release rollup + all 6 CI-stabilization fixes.
test_partial_line_not_consumed asserted +1 byte for the newline, but write_text() text mode translates \n->\r\n on Windows (2 bytes) -> off-by-one, failing windows-setup only. Switched both transcript write sites to write_bytes() for deterministic LF cross-platform. Production _tail_transcript_bytes is already CRLF-safe (reads rb, splits b'\n', strips \r) — test-only fix.
.gitignore exceptions still pointed at templates/builder/ after the TDPLAN-0010 rename (13463c0), so DASHBOARD.local.json + 10 other template dirs/files under templates/aipass_framework/ were silently gitignored — on disk (dirty tree passed) but absent in clean clones/CI. Result: spawn produced no DASHBOARD.local.json and test_full_spawn failed only in a clean checkout. Fixed all 23 .gitignore exception paths + tracked the now-visible template files (all placeholder/seed content: {{BRANCHNAME}}/{{DATE}}/{{CITIZEN_NUMBER}}).
Root-cause fixes for PR#646 red (dev broke after DPLAN-0226/FPLAN-0289/TDPLAN-0010 batch):
- seedgo: branch_audit honors ADVISORY (template_check no longer averaged into gate) + presence_gate added to hooks-snapshot fixture (4 tests)
- hooks: cc_sessions README entry + seedgo modules bypass (reads external ~/.claude, not branch data)
- spawn: retire passport(disabled).py/passport_ops(disabled).py to .archive/ (disabled suffix kept broken cross-import visible to type checker)
- ai_mail: broker-fd test gives testbranch a real .trinity/passport.json for the new marker-walk resolution (f914ab6)
Navmap (@hooks): one bullet in the Memory section — caps are hook-enforced,
the live cap is rendered in each file's *_meta line; read it before writing,
draft to ~80%, one-pass rewrite if rejected. Devpulse branch prompt: same
behavior, explicitly notes caps are NOT listed (single source =
memory.config.json → entry_limits, auto-rendered by @memory's tab_renderer).
Change the config → enforcement + in-file docs follow mechanically; prompts
never go stale.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q3mZT61WsKVN3srCwVDBiW
New 'drone @backup share <file_path> [--public]': uploads a single file to Drive
(AIPass Backups/Shared), sets a read permission (default: restricted to the
authenticated user; --public: anyone-with-link), returns the webViewLink
(webContentLink fallback). Reuses upload_single_file + DriveClient; idempotent
via _find_existing_file; fail-loud on every path. 21 new tests, all Drive API
mocked (zero live calls). Existing commands untouched. DPLAN-0230 v1.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q3mZT61WsKVN3srCwVDBiW
Repurpose the heartbeat into a ~2s transcript-tail loop that edits the "Processing" message in place (block-level: thinking/tool/text), plain text, coalesced, no-op-skipped, 429 retry_after aware, with 4096 rollover. Opt-in per-bot "stream" flag, default OFF; batch path byte-for-byte unchanged. @hooks reviewed: no change needed (already edits processing_message_id for the single-chunk final). Race hardened: re-check delivered before each edit. 37/37 streaming + 653/653 TG tests green; live-proven on the devpulse bot.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q3mZT61WsKVN3srCwVDBiW
--json was routed through Rich console.print(), which defaults to width 80 on
a non-TTY and hard-wraps mid-string, producing invalid JSON (e.g. 'Security
\nScan'). Write raw JSON with sys.stdout.write() in the pass-through paths
(drone.py + router.py); keep Rich for drone's own human UI. Verified live.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EccxENcB3KtyT9XuT4ybPz
New auto-discovered advisory standard: warns (never blocks) when a branch
still carries unrendered template markers, so a citizen that never customized
its scaffold no longer fails silently. Adds template_content.py + template.md
standard doc + test_template_check.py.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EccxENcB3KtyT9XuT4ybPz
beb048d made the Claude bridge propagate a hook's exit code so presence_gate's
UserPromptSubmit block can cancel a prompt. Every security gate
(rm/git/edit/subagent/presence) already returned exit_code 2 for a block, but
the old bridge swallowed it — so test_t2a_rm_gate_blocks pinned exit 0. Update
the e2e contract to expect exit 2 + the stdout decision JSON, which is the real,
live-proven block signal.
Sole CI red on the P1-activation commits: 1 failed, 10116 passed. Fixes both
e2e-wheel (all 3 OSes) and Windows Test (full suite includes tests/e2e).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GmDj4eu8aFFVP2rapovqkg
Final activation fix. The gate recorded os.getpid(), but the hook runs as a
short-lived subprocess (python3 -> sh -> claude) that dies in milliseconds, so
every later session saw the prior holder's PID as dead, reclaimed it, and never
blocked. claim()/release() now resolve the owning session via _resolve_session_pid():
walk the /proc parent chain (PPid from /proc/<pid>/status) up to the comm=claude
ancestor and record THAT pid. Fails OPEN if no claude ancestor (non-Linux, or an
unexpected process tree). handle_stop() is now a no-op: Stop fires every assistant
turn, so releasing there would free the slot mid-session; stale-detection (the
claude pid going away) reclaims on real exit instead.
PROVEN LIVE — real two-session interactive test (the unit blind spot that a
long-lived-holder harness masks):
session 1 in branch X resolves chain 731814:python3 -> 731813:sh -> 730933:claude,
records pid 730933 (comm=claude, cwd=X); work_dir=X, cwd_match True.
session 2 in branch X resolves its own claude pid, sees X occupied by live
730933, and Claude Code blocks the prompt in the UI:
"UserPromptSubmit operation blocked by hook: ztest... already live at PID 730933
- attach, do not spawn."
session 2 did NOT clobber session 1; a different branch is unaffected.
Added 9 tests modelling the ephemeral-PID lifecycle (54 presence tests total);
seedgo @hooks 100%.
Activation is a machine-local provider-settings change (presence_gate wired first
in ~/.claude/settings.json UserPromptSubmit) — not tracked in the repo; the code
landing here is what makes it correct.
Design: DPLAN-0225 / FPLAN-0289 P1. Build by @hooks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CqoxFdbDMirzkQ5kjRVVos
Activation fixes for the single-session presence gate. Two bugs blocked it,
both caught by live testing after all units were green:
1) Wrong branch key. presence_gate used Path.cwd().name, but under the Claude
Code bridge the hook process cwd is the project root, so every session keyed
to "AIPass": the gate never enforced one-live-session-per-branch and would
have rejected sessions project-globally (any 2nd interactive session in any
branch). Now _resolve_branch(hook_data) reads the event payload's cwd (the
real session dir) and walks up to the branch root (.trinity/ or apps/),
mirroring branch_loader. Applied in handle() and handle_stop().
2) Block never reached Claude Code. engine.dispatch() returned only stdout, so
the bridge could not surface a non-zero exit. dispatch() now returns
(stdout, exit_code) and the bridge exits with it on a block. Pre-existing gap
affecting every block hook on every event; now fixed engine-wide. An
intentional block (exit 2 + {"decision":"block"}) propagates; a crashing hook
(exit 2, non-JSON stdout) is logged and falls through, so the gate fails open.
Proven: 110 hooks unit tests pass (6 new for branch resolution); seedgo @hooks
100%, no type errors. Live bridge end-to-end (real live holder + real bridge):
duplicate into a held branch -> exit 2 + block reason naming the branch; a
different free branch -> exit 0 (per-branch isolation intact). Gate remains
dormant: not yet wired into provider settings.
Design: DPLAN-0225 / FPLAN-0289 P1 activation. Build by @hooks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CqoxFdbDMirzkQ5kjRVVos
The Telegram bot becomes a thin durable relay: it follows the live Claude session
via .ai_central/PRESENCE.central.json and never starts its own brain.
ensure_tmux_session resolves in 3 strategies (central pointer -> shared_session
config -> already-running own tmux), re-binding to the live session on every message
(handover-safe). The legacy AIPASS_SESSION_TYPE=telegram own-session spawn is retired:
replaced with a clear "no live session to mirror" error; an absent/stale pointer falls
back gracefully and never starts a session. on_session_create (which injected "hi"
after self-start) removed as obsolete -- attaching to a live session injects nothing.
New helpers: _find_presence_file, _read_presence_pointer (PID-liveness via os.kill),
_find_tmux_for_presence (attach_handle preferred, tmux-CWD-scan fallback).
601 TG + 252 skills tests pass; seedgo Unused_Function 100% (overall 99%; residual is
a pre-existing Json_Handler item in unrelated modules). Live mirror proof to follow.
Design: DPLAN-0225 / FPLAN-0289 P2. Build by @skills.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GFihce1oLtp6UDAPGryYSv
Windows CI was red on f460cd5: the patch_flock fixture patched presence.fcntl,
which only exists on POSIX (msvcrt on win32), erroring all 16 presence-test
setups. Now patches the platform-agnostic _presence_lock context manager
(-> nullcontext per call) and skips the inherently-POSIX flock-acquire test on
win32. Dormant prod code unchanged.
705 tests pass, seedgo 100%. Fix by @hooks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GFihce1oLtp6UDAPGryYSv
One live Claude runtime per branch. presence.py manages .ai_central/PRESENCE.central.json
(claim/release/refresh, PID + /proc/cwd liveness, stale-reclaim, PID-guarded release so a
non-holder can never release the holder). presence_gate.py: UserPromptSubmit blocks a
duplicate (exit 2 + decision:block), Stop releases; skips sub-agents + dispatched/daemon.
SessionStart can't block in Claude Code (inject-only) → gate is UserPromptSubmit, like the
edit/git gates. NOT wired into hooks.json yet — dormant, zero behavior change until enabled.
705 tests pass, seedgo 100%. Live cross-process block + PID-guard verified (devpulse).
Design: DPLAN-0225. Next: P2 telegram relay follows the pointer (@skills).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GFihce1oLtp6UDAPGryYSv
base_bot.py _resolve_active_transcript: slug replaces both backslash and / (Windows work_dir paths left backslashes → wrong projects_dir; POSIX no-op). test_mirror_session.py: slug matches production + mkdir exist_ok=True (dir pre-created on Windows → WinError183). test_monitor.py: mock _save_monitor_subscription→False instead of /dev/null OSError trick. 578 TG + 252 skills green on Linux; Windows-safe by construction. Fix by @skills, verified by devpulse.
readme_check.py: _count_test_functions now skips any path with a SOURCE_SKIP_DIRS segment (.archive) — root cause of the local-vs-CI test-count mismatch (daemon counted 486 local incl archived dead tests vs 300 in CI clean checkout). test_quality_check.py: _find_test_files_broad replaced __pycache__-only skip with _should_skip_dir() on all path parts. Daemon 486→300, audit 100%, 17-branch audit zero regressions. CI-neutral (clean checkout has no .archive). Fix by @seedgo, verified by devpulse.
CI's clean checkout counts 300 live tests (matches pytest); README claimed 486 because the count included 6 archived dead-test files under tests/.archive/ (186 tests). 300 is the true live/CI count. Root-cause framework fix (audit must always ignore .archive) dispatched to @seedgo separately.
- run() called a non-existent self._poll_loop() AND duplicated the parent lifecycle incompletely (missing signal handlers, offset load/save) — bot exited 1 in <1s under systemd. Now wraps super().run() with digest start/stop only.
- Fixed broken 'from .json import json_handler' (relative path did not exist) → absolute import; log queue_requested op so json_structure standard is met by use, not noqa
- Added missing docstrings: handle_message / handle_file / get_custom_commands
- ROOT CAUSE: 26 unit tests never executed run() (it blocks on polling) → green tests, failing ExecStart (key-learning #77: test the real ExecStart, not the drone/mocked form). Verified live: bot now active+polling via systemd, 26 tests + seedgo 30 still green.
- One queue: archive dormant task_registry + actions_registry (+5 tests) to .archive/, retire schedule/actions CLIs; .daemon/schedule.json is now the single source
- Status capture: runstate gains last_status/last_error/last_success_at/last_failure_at; persisted on success AND failure paths (was success-only)
- Unified view: drone @daemon queue + --json (frozen schema), aggregates .daemon/*.json joined to runstate
- Lifecycle pings: un-archived telegram_notifier wired to @skills send_telegram_notification (fail-soft, per-job notify flag, zero calls on empty ticks)
- 24 new tests (327 pass), seedgo 98%; verified live end-to-end (queue --json schema + real telegram delivery via daemon wrapper)
DPLAN-0218 pulled telegram into the seedgo gate, surfacing 16 unused_function
flags across 8 handlers. They are ported-but-unwired (S249), not dead — pending
DPLAN-0220 wiring. Added name-scoped unused_function bypasses citing DPLAN-0220,
documented each in SKILL.md -> Ported-but-unwired (remove bypass as wired).
@skills 100%.
core.py adopt-path read the passport via json.loads(read_text()) — a direct
file op that fails the json_handler standard and the CI seedgo-audit gate.
Switch to json_handler.read_json() (matches the pattern ~90 lines above),
drop the now-unused 'import json as _json'. @spawn 100%; 315 spawn tests green.
Guarding the fcntl import let Windows collection succeed, which surfaced 3
telegram tests that had never run on Windows — all test-portability bugs:
- log_streamer byte-count broke on CRLF -> fixture writes newline=''
- bot_registry write-failure used Unix-only /proc -> file-as-parent (all OS)
- validate_bot_config rejected POSIX work_dir on Windows (Path.is_absolute is
host-dependent) -> test absoluteness under PurePosixPath OR PureWindowsPath
493 telegram tests green on Linux; ruff clean.
bot_registry did a bare 'import fcntl' (POSIX-only); on Windows the 8
telegram test modules importing it failed at collection (ModuleNotFoundError),
reddening Windows Test on recent PRs. Guard the import and route flock calls
through no-op-on-Windows _lock/_unlock helpers. 246 telegram tests green.