diff --git a/.claude/settings.local.json b/.claude/settings.local.json index b77477b..e9ee218 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -29,5 +29,18 @@ "Bash(node -e \"const p = require\\(''C:/source/teammates/node_modules/vscode-jsonrpc/package.json''\\); console.log\\(p.version, p.exports ? ''has exports'' : ''no exports''\\)\")", "Bash(node:*)" ] + }, + "hooks": { + "PostToolUse": [ + { + "matcher": "", + "hooks": [ + { + "type": "command", + "command": "node -e \"\" /* teammates-activity */" + } + ] + } + ] } } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a94d27..c933dd6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,6 +63,12 @@ jobs: - name: Lint run: npm run lint + - name: Build independent packages + run: npm run build -w packages/consolonia & npm run build -w packages/recall & wait + + - name: Build CLI (depends on consolonia + recall) + run: npm run build -w packages/cli + - name: Type-check run: npm run typecheck diff --git a/.teammates/.gitignore b/.teammates/.gitignore index 4a919af..a9aa5f6 100644 --- a/.teammates/.gitignore +++ b/.teammates/.gitignore @@ -1,4 +1,5 @@ USER.md **/.index/ +**/SYSTEM-PROMPT.md .*/ .tmp/ diff --git a/.teammates/CROSS-TEAM.md b/.teammates/CROSS-TEAM.md index 80a9025..2a76472 100644 --- a/.teammates/CROSS-TEAM.md +++ b/.teammates/CROSS-TEAM.md @@ -43,3 +43,5 @@ Active projects are tracked in **[PROJECTS.md](PROJECTS.md)** — codename, spec - **[Recall Query Architecture](scribe/docs/specs/F-recall-query-architecture.md)** — Two-pass recall design: LLM-free priming pass + agent-driven mid-task search. Solves the chicken-and-egg query problem. _(added 2026-03-21)_ - **[Collision Prevention](scribe/docs/specs/F-collision-prevention.md)** — 5-layer defense model for preventing code overwrites in multi-human + multi-agent repos: branches, worktrees, ownership routing, active claims, merge queues. _(added 2026-03-22)_ - **[Interrupt and Resume](scribe/docs/specs/F-interrupt-and-resume.md)** — Checkpoint/restore for agent timeouts: kill agent, capture conversation log, replay with user steering as new prompt. Manual `/interrupt` command + automatic timeout-triggered resume. _(added 2026-03-27)_ +- **[Thread View Redesign](scribe/docs/specs/F-thread-view-redesign.md)** — ThreadContainer abstraction, inline verbs on subject lines, thread-level [reply]/[copy thread], simplified input routing. _(added 2026-03-28)_ +- **[Widget Model Redesign](scribe/docs/specs/F-widget-model-redesign.md)** — Identity-based FeedItem model + VirtualList widget extraction to eliminate brittle index-shifting in ChatView. _(added 2026-03-29)_ diff --git a/.teammates/PROTOCOL.md b/.teammates/PROTOCOL.md index f05e07b..7a08bf9 100644 --- a/.teammates/PROTOCOL.md +++ b/.teammates/PROTOCOL.md @@ -125,15 +125,16 @@ The CLI automatically builds each teammate's context before every task. The prom The prompt stack (in order): 1. **SOUL.md** — identity, principles, boundaries (always, outside budget) -2. **WISDOM.md** — distilled principles from compacted memories (always, outside budget) -3. **Relevant memories from recall** — automatically queried using the task prompt; returns matching episodic summaries and typed memories from the vector index (at least 8k tokens, plus any unused daily log budget) -4. **Recent daily logs** — today's log is always included; days 2-7 are included most-recent-first up to 24k tokens (whole entries only, never truncated mid-entry) -5. **Session state** — path to the session file (`.teammates/.tmp/sessions/.md`); the agent reads and writes it directly for cross-task continuity -6. **Roster** — all teammates and their roles -7. **Memory update instructions** — how to write daily logs, typed memories, and WISDOM.md -8. **Output protocol** — response format and handoff syntax -9. **Current date/time** -10. **Task** — the user's message (always, outside budget) +2. **GOALS.md** — active objectives and priorities (always, outside budget) +3. **WISDOM.md** — distilled principles from compacted memories (always, outside budget) +4. **Relevant memories from recall** — automatically queried using the task prompt; returns matching episodic summaries and typed memories from the vector index (at least 8k tokens, plus any unused daily log budget) +5. **Recent daily logs** — today's log is always included; days 2-7 are included most-recent-first up to 24k tokens (whole entries only, never truncated mid-entry) +6. **Session state** — path to the session file (`.teammates/.tmp/sessions/.md`); the agent reads and writes it directly for cross-task continuity +7. **Roster** — all teammates and their roles +8. **Memory update instructions** — how to write daily logs, typed memories, and WISDOM.md +9. **Output protocol** — response format and handoff syntax +10. **Current date/time** +11. **Task** — the user's message (always, outside budget) Weekly summaries are **not** injected directly — they are searchable via recall (step 3) and surface when relevant to the task prompt. @@ -165,7 +166,7 @@ See [TEMPLATE.md](TEMPLATE.md) for full format, body structure per type, and exa ### Tier 3 — Wisdom -`WISDOM.md` — Distilled, high-signal principles derived from compacting multiple memories. Compact, stable, rarely changes. Read second (after SOUL.md). +`WISDOM.md` — Distilled, high-signal principles derived from compacting multiple memories. Compact, stable, rarely changes. Read after SOUL.md and GOALS.md. ### Compaction @@ -225,7 +226,7 @@ The CLI uses this convention to detect teammates: any child directory without a ## Adding New Teammates -1. Copy the SOUL.md and WISDOM.md templates from [TEMPLATE.md](TEMPLATE.md) to a new folder under `.teammates/` +1. Copy the SOUL.md, GOALS.md, and WISDOM.md templates from [TEMPLATE.md](TEMPLATE.md) to a new folder under `.teammates/` 2. Fill in all sections with project-specific details 3. Update README.md roster, last-active date, and routing guide 4. Update existing teammates' SOUL.md ownership and boundary sections if domains shift diff --git a/.teammates/README.md b/.teammates/README.md index 084e61c..046722c 100644 --- a/.teammates/README.md +++ b/.teammates/README.md @@ -29,7 +29,7 @@ Scribe defines the framework structure (templates, onboarding instructions). Bea | Keywords | Teammate | |---|---| -| strategy, roadmap, specs, planning, priorities, docs, documentation, template, onboarding, SOUL.md, WISDOM.md, protocol, framework, roster, markdown | **Scribe** | +| strategy, roadmap, specs, planning, priorities, docs, documentation, template, onboarding, SOUL.md, GOALS.md, WISDOM.md, protocol, framework, roster, markdown | **Scribe** | | recall, search, embeddings, vectra, indexer, vector, semantic, cli, orchestrator, adapter, REPL, handoff, agent, routing, queue, consolonia, terminal UI, code, implementation, bug fix, feature | **Beacon** | | ci, cd, pipeline, workflow, actions, release, publish, deploy, build automation, shipping, containers, infrastructure | **Pipeline** | @@ -44,7 +44,8 @@ Every child folder of `.teammates/` is interpreted by its name prefix: Each teammate folder contains: - **SOUL.md** — Identity, continuity instructions, principles, boundaries, capabilities, and ownership -- **WISDOM.md** — Distilled principles from compacted memories (read second, after SOUL.md) +- **GOALS.md** — Active objectives and priorities (read after SOUL.md) +- **WISDOM.md** — Distilled principles from compacted memories (read after GOALS.md) - **memory/** — Daily logs (`YYYY-MM-DD.md`), typed memory files (`_.md`), and episodic summaries (`weekly/`, `monthly/`) Shared folders: diff --git a/.teammates/TEMPLATE.md b/.teammates/TEMPLATE.md index f108f5a..2b22a66 100644 --- a/.teammates/TEMPLATE.md +++ b/.teammates/TEMPLATE.md @@ -1,6 +1,6 @@ # New Teammate Template -Copy the SOUL.md, WISDOM.md, and RESUME.md structures below to `.teammates//` and fill in each file. Create an empty `memory/` directory (with `weekly/` and `monthly/` subdirectories) for daily logs, episodic summaries, and typed memory files. +Copy the SOUL.md, GOALS.md, WISDOM.md, and RESUME.md structures below to `.teammates//` and fill in each file. Create an empty `memory/` directory (with `weekly/` and `monthly/` subdirectories) for daily logs, episodic summaries, and typed memory files. --- @@ -21,11 +21,12 @@ Do what you're told. If the task is unclear, ask clarifying questions — but ex Each session, you wake up fresh. These files _are_ your memory. Read them. Update them. They're how you persist. -- Read your SOUL.md and WISDOM.md at the start of every session. +- Read your SOUL.md, GOALS.md, and WISDOM.md at the start of every session. - Read `memory/YYYY-MM-DD.md` for today and yesterday. - Read USER.md to understand who you're working with. - Relevant memories from past work are automatically provided in your context via recall search. - Update your files as you learn. If you change SOUL.md, tell the user. +- Keep GOALS.md current — mark goals done as you complete them, add new ones as they emerge. ## Core Principles @@ -96,6 +97,38 @@ _(No wisdom yet — principles emerge after the first compaction.)_ --- +## GOALS.md Template + +GOALS.md tracks what a teammate is actively working towards — *intent* and *direction*. Read it each session after SOUL.md and WISDOM.md. Keep it scannable and current. + +```markdown +# — Goals + +Updated: YYYY-MM-DD + +## Active Goals + +### P0 — Current Sprint + +- [ ] + +### P1 — Up Next + +- [ ] + +### P2 — Backlog + +- [ ] + +## Completed + +- [x] — done YYYY-MM-DD +``` + +**Guidelines:** One line per goal. Link to specs when they exist. Mark done inline with date, don't delete. Update at end of each session. See `template/TEMPLATE.md` for full format details. + +--- + ## Daily Log Template Daily logs live at `.teammates//memory/YYYY-MM-DD.md`. They are append-only and capture what happened during a session. diff --git a/.teammates/_standups/2026-03-28.md b/.teammates/_standups/2026-03-28.md new file mode 100644 index 0000000..05b8cde --- /dev/null +++ b/.teammates/_standups/2026-03-28.md @@ -0,0 +1,21 @@ +# Standup — 2026-03-28 + +## Scribe — 2026-03-28 + +### Done (since last standup 03-25) +- **Thread View Redesign spec** — Designed ThreadContainer abstraction, inline verbs, thread-level [reply]/[copy thread], conversation history owned by threads, auto-reply to bottom thread. Handed off to Beacon (03-28) +- **Interrupt-and-resume spec** — Designed 3-phase interrupt/resume mechanism for agent timeouts (manual `/interrupt`, auto-interrupt at 80% timeout, log compaction for long sessions), handed off to Beacon (03-27) +- **Timeout root cause analysis** — Analyzed bard hang in loreweave; identified bulk file creation (42 files) exceeding 600s timeout as true root cause (03-27) +- **Wisdom compaction** — Added 2 new entries ("Spec bulk operations with batch limits" and "Design for interruption"), verified all 17 entries current (03-27–03-28) +- **Compressed daily logs** — Compressed 14 daily log files (03-13 through 03-27) to save context window space (03-27) + +### Next +- Thread View Redesign — track Beacon implementation, update docs when shipped +- P1 Parity doc updates as Beacon ships S16/S17/S26 implementations +- Campfire v0.5.0 Phase 1 doc readiness (twin folders, heartbeat, handoff queue) +- Spec refinements from Beacon implementation feedback on interrupt-and-resume + +### Blockers +- None + +--- diff --git a/.teammates/_standups/2026-03-29.md b/.teammates/_standups/2026-03-29.md new file mode 100644 index 0000000..9a7cf43 --- /dev/null +++ b/.teammates/_standups/2026-03-29.md @@ -0,0 +1,46 @@ +# Standup — 2026-03-29 + +_Rerun requested again on 2026-03-29. Current sections received: Scribe, Beacon. Pending: Lexicon, Pipeline._ + +## Scribe — 2026-03-29 + +### Done (since last standup 03-28) +- **Thread view post-mortem** — Analyzed why thread view took 18 rounds. Root causes: spec-after-code, no index tests, serial visual feedback, 6,800-line cli.ts, WISDOM bloat. Delivered 6 recommendations (03-29) +- **Migration v0.5.0 → v0.7.0** — Applied 0.6.0 compression + 0.7.0 version bumps across all 22 memory files. Scrubbed system task entries, capped WISDOM.md (03-29) +- **/Command rationalization** — Audited all 16 slash commands. Proposed removing 3 (/compact, /copy, /theme), renaming 1 (/init → /setup), adding 3 (/add, /remove, /update). Open questions remain on /script, /configure, /retro (03-29) + +### Next +- Finalize slash command decisions once user resolves open questions +- Thread View Redesign — track Beacon implementation, update docs when shipped +- P1 Parity doc updates as Beacon ships S16/S17/S26 +- Spec refinements from Beacon feedback on interrupt-and-resume + +### Blockers +- None + +--- + +## Beacon — 2026-03-29 + +### Done (since last standup 03-28) +- **cli.ts extraction (Phase 1+2)** — Pulled 7 modules out of cli.ts, shrinking it from 6,815 to 4,159 lines (-39%): status-tracker, handoff-manager, retro-manager, wordwheel, service-config, thread-manager, onboard-flow +- **StatusTracker redesign** — Clean 3-method API: `startTask/stopTask/showNotification`. Notifications are one-shot styled messages. Animation lifecycle fully private. Updated 15+ call sites +- **Migration system** — Built `MIGRATIONS.md`-based upgrade system. Parses markdown sections, filters by version, queues one agent task per teammate. Progress via StatusTracker. Interruption-safe (re-runs on next startup) +- **Thread view fixes** — Fixed off-by-one in `_shiftFeedIndices`, [reply] verb, reply indentation, handoffs rendering outside containers, @ prefix removal, header spacing +- **Activity tracking** — Real-time agent activity via PostToolUse hook system. `[show activity]` toggle + `[cancel]` verbs on working placeholders. Hook auto-installs at startup. Fixed 3 follow-up bugs (missing details, no visual feedback, persistent lines) +- **System task isolation** — `system` flag on TaskAssignment threads through to prompt builder. System tasks skip daily log/memory instructions + +### Next +- Build & verify activity hook end-to-end +- Slash command extraction if cli.ts entanglement can be resolved +- Start on hooks system (CP1) once N1 extraction stabilizes + +### Blockers +- None + +--- + +## Pending + +- **Lexicon** — standup requested, response pending +- **Pipeline** — standup requested, response pending diff --git a/.teammates/_standups/2026-03-30.md b/.teammates/_standups/2026-03-30.md new file mode 100644 index 0000000..8192f68 --- /dev/null +++ b/.teammates/_standups/2026-03-30.md @@ -0,0 +1,43 @@ +# Standup — 2026-03-30 + +## Scribe — 2026-03-30 + +### Done (since last standup 03-29) +- **Widget model redesign spec** — Designed identity-based FeedItem model + FeedStore + VirtualList extraction to replace 5 parallel index-keyed structures in ChatView. 4-phase migration plan. Handed off to Beacon (03-29) +- **Template improvement analysis** — Compared live `.teammates/` against `template/`, identified 9 gaps. Drafted GOALS.md as first deliverable (03-29) +- **GOALS.md template + propagation** — Created GOALS.md template in TEMPLATE.md (v2→v3), example file, and propagated across all 12 docs that reference the file structure (03-29) +- **README sandboxing note** — Added workspace sandbox initialization reminder to Getting Started (03-29) + +### Next +- Finalize slash command decisions (open questions on /script, /configure, /retro) +- Track Beacon implementation of widget model redesign + thread view redesign +- Remaining template improvements from analysis (daily log frontmatter, docs/specs/ convention) + +### Blockers +- None + +--- + +## Beacon — 2026-03-30 + +### Done (since last standup 03-29) +- **Win32 mouse fix** — Root-caused why terminal verbs weren't clickable on Windows: Node.js doesn't disable Quick Edit Mode or manage console flags. Implemented `win32-console.ts` using koffi FFI to call `SetConsoleMode`. Iterated twice — first pass incorrectly enabled `ENABLE_MOUSE_INPUT` (conflicts with VT sequences), second pass removed it and forced `ENABLE_VIRTUAL_TERMINAL_INPUT` +- **Full mouse protocol support** — Added classic xterm (`CSI M`), URXVT (`?1015h`), UTF-8 (`?1005h`), and SGR-Pixels (`?1016h`) mouse protocols alongside existing SGR +- **Environment-aware terminal init** — Created `terminal-env.ts` with `detectTerminal()` that probes env vars to identify terminal capabilities. Init/restore sequences now adapt to detected terminal +- **Copilot activity parsing expansion** — Expanded `mapCopilotToolCall` from 8 to 20+ tool names (powershell, task, read_agent, web_search, github-mcp-server-*, plumbing filtering) +- **Clean rebuild + version bumps** — 0.7.0 → 0.7.1 → 0.7.2 → 0.7.3. All 1,000 tests passing (602 consolonia + 94 recall + 304 cli) + +### Next +- User to test mouse clicks after CLI restart +- Widget model redesign (from Scribe spec) +- Continued cli.ts extraction + +### Blockers +- None + +--- + +## Pending + +- **Lexicon** — standup requested, response pending +- **Pipeline** — standup requested, response pending diff --git a/.teammates/beacon/WISDOM.md b/.teammates/beacon/WISDOM.md index 0b2a054..c00c637 100644 --- a/.teammates/beacon/WISDOM.md +++ b/.teammates/beacon/WISDOM.md @@ -1,127 +1,219 @@ -# Beacon — Wisdom +# Beacon - Wisdom Distilled principles. Read this first every session (after SOUL.md). -Last compacted: 2026-03-28 +Last compacted: 2026-04-02 --- -### Codebase map — three packages -CLI has 25 source files (~6,000 lines in cli.ts); consolonia has 51 files; recall has 13 files. The big files are `cli.ts` (~6,000 lines), `chat-view.ts` (~1,520 lines), `markdown.ts` (~970 lines), and `cli-proxy.ts` (~810 lines). Key extracted modules: `adapter.ts` (~560), `compact.ts` (~800), `banner.ts` (~410), `log-parser.ts` (~290), `cli-utils.ts` (~195), `cli-args.ts` (~155), `personas.ts` (~140). When debugging, start with cli.ts and cli-proxy.ts. +## Prompt & Context -### Three-tier memory system -WISDOM.md (distilled, read-only except during compaction), typed memory files (`memory/_.md`), and daily logs (`memory/YYYY-MM-DD.md`). The CLI reads WISDOM.md, the indexer indexes WISDOM.md + memory/*.md, and the prompt tells teammates to write typed memories. +**Prompt pipeline is two-tier: static system prompt + dynamic user message** +Each teammate has a pre-built `SYSTEM-PROMPT.md` (generated at startup by `system-prompt.ts`). It contains all stable sections: IDENTITY, GOALS, WISDOM, TEAM, SERVICES, RECALL_TOOL, ENVIRONMENT, USER_PROFILE, INSTRUCTIONS. Claude receives this via `--append-system-prompt-file` (preserves Claude Code's built-in system prompt). The dynamic user message carries conversation history, daily log snapshot, recalled memories, handoff context, and the task. Non-Claude agents get the full prompt combined via stdin. -### Memory frontmatter convention -All memory files include YAML frontmatter with `version: 0.6.0` as the first field. Daily logs add `type: daily`, typed memories add their type. Metadata fields pass through to the model intact — no stripping. Compression prompts and adapter instructions both enforce this convention. +**Prompt stack order is IDENTITY → GOALS → WISDOM → TEAM → ...** +GOALS.md was added between SOUL.md and WISDOM.md in `buildTeammatePrompt()`. Every `TeammateConfig` must include a `goals` field (empty string if no file exists). Missing it causes `undefined.trim()` crashes. -### Context window budget model -Target context window is 128k tokens. Fixed sections always included (identity, wisdom, today's log, roster, protocol, USER.md). Daily logs (days 2-7) get 12k token pool. Recall gets min 8k + unused daily budget, with 4k overflow grace. Conversation history budget is derived dynamically: `(TARGET_CONTEXT_TOKENS - PROMPT_OVERHEAD_TOKENS) * CHARS_PER_TOKEN`. Weekly summaries excluded (recall indexes them). USER.md placed just before the task. +**User message budget is 20k tokens, priority-ordered** +Replaced the old dual-budget system (12k daily logs + 8k recall). Priority order: (1) user's message (unbounded), (2) conversation history (highest), (3) daily log snapshot from before conversation started (medium), (4) recalled memories in `MEMORY:` format (lowest). As conversation grows, it naturally pushes out recall and daily logs. `buildUserMessage()` handles allocation. -### Prompt section ordering — instructions at the end -Context/reference material (identity, wisdom, logs, recall, roster, services, handoff, date/time, user profile) stays at the top. Task sits in the middle. Instructions (output protocol, session state, memory updates, reminder) go at the end — leverages recency effect for agent attention. +**Conversation context stays inline** +Do not offload conversation history into temp markdown files. Inline context is more reliable, avoids concurrent file races, and works better with deterministic compression. Pre-dispatch compression keeps history within budget. -### Attention dilution defenses -Five fixes to prevent agents from spending all tool calls on housekeeping instead of the task: (1) Dedup recall against daily logs already in the prompt. (2) Daily log budget halved (24K→12K) — past logs are reference, not active context. (3) Echo user's request at bottom of instructions (<500 chars verbatim, else pointer). (4) Task-first priority statement at top of instructions. (5) Conversation context always inlined in the prompt (file offload removed — pre-dispatch compression keeps it within budget). +**Concurrent fan-out needs snapshots** +When dispatching `@everyone` or any parallel queue, capture immutable `conversationHistory` and `conversationSummary` per entry at queue time. Shared mutable context will bleed across drain loops. -### Two-stage conversation compression -**Pre-dispatch (mechanical):** `preDispatchCompress()` runs before every task dispatch — if history exceeds budget, oldest entries are mechanically compressed into bullet summaries via `compressConversationEntries()`. **Post-task (quality):** `maybeQueueSummarization` still runs async for better summaries. The running summary is invisible to the user. Reset on `/clear`. +**Empty-response defense is layered** +Use response-first prompting, retry in raw mode when `rawOutput` is empty, and synthesize a fallback from `changedFiles` plus `summary` if needed. Reject lazy bodies and stale session recaps. -### Conversation history stores full bodies -`storeResult()` stores the full cleaned `rawOutput` (protocol artifacts stripped), not just `result.summary`. `buildConversationContext()` formats multi-line entries with body on the next line. When history exceeds token budget, pre-dispatch compression fires before the next task. +**Task behavior flags are purpose-specific** +`system` suppresses memory updates and feed output for maintenance tasks. `skipMemoryUpdates` suppresses only memory instructions (used by `/btw` for ephemeral questions). `raw` skips `buildTeammatePrompt` entirely for retries. Do not overload one flag for multiple purposes. -### @everyone concurrent dispatch — snapshot isolation -`queueTask()` freezes `conversationHistory` + `conversationSummary` into a `contextSnapshot` once before pushing all @everyone entries (each gets a shallow copy). `drainAgentQueue()` skips `preDispatchCompress()` when an entry has a snapshot and passes it directly to `buildConversationContext()`. Context is always inlined (no file offload). This prevents race conditions where the first drain loop mutates shared state before concurrent drains read it. +**Session state belongs in-process, not in files** +Do not persist session state to per-teammate markdown files. Session files waste tokens, create phantom agent activity, and add no value over inline conversation context. The Orchestrator's in-memory `sessions` Map is sufficient. -### User avatar system (Campfire Phase 1) -Users are represented as avatar teammates with `**Type:** human` in SOUL.md. The adapter is hidden — not registered when user has an alias. `selfName` (user alias) is the display identity everywhere; `adapterName` is for internal execution only. `@everyone` excludes both avatar and adapter. Display surfaces (roster, picker, status, errors) show `adapterName` while `selfName` is used for sender label, conversation history, internal routing, and memory folder. Import skips human avatar folders (checks SOUL.md for `**Type:** human`). +**Pre-dispatch compression is mechanical** +`preDispatchCompress()` runs before every task dispatch — if conversation history exceeds the budget (96k tokens / 384k chars), it mechanically compresses the oldest entries into bullet summaries. Async agent summarization runs post-task for quality. Keep both paths; they serve different timing needs. -### Onboarding happens pre-TUI -User setup (GitHub or manual) runs before the TUI is created via `console.log` + `askInput`/`askChoice`. No mouse tracking issues. Team onboarding only runs if `.teammates/` was missing. `askInline()` is used for in-TUI prompts (e.g., `/configure`) to avoid stdin conflicts with consolonia. Persona templates (`packages/cli/personas/`) provide scaffolding — `/init pick` for in-TUI selection. +## Memory & Persistence -### Assignment works via @mention, not /assign -No `/assign` slash command. Assignment goes through `queueTask()`. Multi-mention dispatches to all mentioned teammates. Paste @mentions are pre-resolved from raw input before placeholder expansion to prevent routing on pasted content. +**Memory is three-tier** +WISDOM.md stores durable rules, typed memories store reusable decisions and feedback, and daily logs store chronology. Keep full YAML frontmatter in prompt context; the metadata is part of the memory. -### Default routing follows last responder -Un-mentioned messages route to `lastResult.teammate` first, then `orchestrator.route()`, then `selfName`. Explicit `@mentions` always override. +**System tasks must not write memories** +Maintenance work like compaction, summarization, and wisdom distillation should not touch daily logs or typed memories. The prompt path must explicitly suppress memory-update instructions for system tasks. -### Route threshold prevents weak matches -`Orchestrator.route()` requires a minimum score of 2 (at least one primary keyword match). Single secondary keyword matches (score 1) fall through. +**Migrations are markdown and commit last** +Keep upgrade instructions in `packages/cli/MIGRATIONS.md`, parse them by version heading, and persist the new version only after every migration succeeds. Interrupted upgrades should rerun cleanly on next startup. Resolve the file via `import.meta.url`, never `__dirname`. -### Recall two-pass architecture -**Pass 1 (pre-task, no LLM):** `buildQueryVariations()` generates 1-3 queries from task + conversation context. `matchMemoryCatalog()` does frontmatter text matching. `multiSearch()` fuses results with dedup by URI. **Pass 2 (mid-task):** Every teammate prompt includes a recall tool section documenting `teammates-recall search` CLI usage for agent-driven iterative queries. +**Wisdom distillation must be idempotent** +`buildWisdomPrompt()` checks `Last compacted: YYYY-MM-DD` in WISDOM.md. If today's date is already present, return `null` (skip). Without this guard, wisdom distillation fires on every startup, burning tokens for no reason. -### Empty response defense — three layers -1. **Two-phase prompt** — Output protocol before session/memory instructions; agents write text first, then do housekeeping. 2. **Raw retry** — If `rawOutput` is empty and `success` is true, fire retry with `raw: true` (no prompt wrapping). Second retry with minimal "just say Done" prompt. 3. **Synthetic fallback** — `displayTaskResult` generates body from `changedFiles` + `summary` metadata when text is still empty. +**Memory files must not include version: in frontmatter** +The `version:` field was removed from all memory file frontmatter and from the code that generates it (`compact.ts`, `adapter.ts`). Version is tracked centrally in `.teammates/settings.json` (`cliVersion`), not per-file. -### Handoff format requires fenced code blocks -Agents must use ` ```handoff\n@name\ntask\n``` ` format. Natural-language handoff fallback (`findNaturalLanguageHandoffs()`) catches "hand off to @name" patterns as a safety net, but only fires when zero fenced blocks are found. +**Index versioning triggers full rebuilds** +`indexVersion` in `.teammates/settings.json` tracks the index format. `StartupManager.INDEX_VERSION` is the current expected version. When the persisted version is lower (or missing), startup runs `indexer.indexAll()` for a full rebuild instead of incremental sync. Bump `INDEX_VERSION` whenever the indexing format changes (e.g., chunking was v1→v2). -### Recall is bundled infrastructure -`@teammates/recall` is a direct dependency of `@teammates/cli`. Pre-task recall queries use `skipSync: true` for speed. Sync runs after every task completion and on startup. No watch process needed. +**User twin gets logged and compacted** +`logUserTask()` writes task entries to the user's twin daily memory (`.teammates//memory/YYYY-MM-DD.md`) after each task completes. The user's twin is included in startup compaction, daily compression, and stale daily purge cycles. Logging is fire-and-forget to never block task flow. -### Banner is segmented — left footer + right footer -Left: product name + version + adapter name + project directory path (smart-truncated via `truncatePath()`). Right: `? /help` by default, temporarily replaced by ESC/Ctrl+C hints. Services show presence-colored dots (green/yellow/red). `updateServices()` refreshes the banner live after `/configure`. +## Recall & Search -### Debug logging lives in .tmp/debug/ -Every task writes a structured debug log to `.teammates/.tmp/debug/-.md` including the full prompt sent to the agent (via `fullPrompt` on `TaskResult`). Files >24h are cleaned on startup. `/debug [teammate] [focus]` reads the last log and queues analysis to the coding agent — optional focus text narrows the analysis scope. Adapters set `result.fullPrompt` after building the prompt; `lastTaskPrompts` stores it for `/debug`. +**Recall indexes markdown in chunks** +`chunker.ts` splits memory files into ~2k token chunks (~8k chars) on markdown heading boundaries, then paragraph boundaries. Each chunk is a separate Vectra document with URI suffix `#0`, `#1`, etc. `classifyUri()` strips `#N` suffix before classification. `isChunkUri()` and `uriToRelativePath()` are the helpers. -### Two-tier compaction — scheduled + budget-driven -`compactDailies()` runs on startup for completed past weeks. `autoCompactForBudget()` runs pre-task in adapters when daily logs exceed `DAILY_LOG_BUDGET_TOKENS` (12k) — it compacts oldest weeks first, including the current week with `partial: true` frontmatter. Partial weeklies are merged by `compactDailies()` when more dailies arrive. Startup compaction uses silent mode — progress bar only unless actual work was done. `runCompact()` also triggers `autoCompactForBudget` before episodic compaction. +**Recalled memories use structured MEMORY: format** +Results are formatted as `MEMORY:` blocks with `file:`, `type:`, `period:`, `partial:` metadata fields. This replaces the old `` XML block in the system prompt — recall results now go in the user message under the budget system. -### Daily compression via system tasks -`buildDailyCompressionPrompt()` checks if yesterday's log needs compression on new day boundary. Compressed logs marked with `compressed: true` frontmatter. Keeps task headers + one-line summaries + key decisions + file lists (3-5 lines per task). `buildMigrationCompressionPrompt()` handles bulk compression of historical logs during version migration. +**SYSTEM-PROMPT.md is generated, not committed** +`**/SYSTEM-PROMPT.md` is gitignored. Generated at startup by `writeAllSystemPrompts()` in `system-prompt.ts`. Claude uses it directly via `--append-system-prompt-file`. Falls back to inline generation for tests and first-run scenarios. -### Version tracking and migration -`checkVersionUpdate()` is read-only; `commitVersionUpdate()` writes. Version persisted LAST — only after all migration tasks complete (or immediately if no migration needed). Migration logic (v0.6.0): finds uncompressed dailies per teammate, queues system tasks with `migration: true`, re-indexes after all complete via `pendingMigrationSyncs` counter. `semverLessThan()` is a reusable utility for future migrations. +## Feed & Rendering -### Non-blocking system task lane -System-initiated tasks (compaction, summarization, wisdom distillation) run concurrently without blocking user tasks via task-level `system` flag on `TaskAssignment` and `TaskResult`. An agent can run 0+ system tasks and 0-1 user tasks simultaneously. System tasks use unique `sys--` IDs, tracked in `systemActive` map. `kickDrain()` extracts them from the queue before processing user tasks. System tasks are fully background — no progress bar, no `/status` display, errors only (with `(system)` label in the feed). The `system` flag on events allows concurrent system + user tasks for the same agent without interference. +**Feed state should be identity-based** +Inside `ChatView`, track feed items by stable IDs through `FeedStore`, not parallel index-keyed arrays. `FeedItem` carries `id`, `content`, `actions`, `hidden`. Height caching lives in `VirtualList`, not on `FeedItem`. -### Progress bar — 80-char target with elapsed time -Active user tasks display as ` ... (2m 5s)`. Format targets 80 chars total — task text is dynamically truncated to fit. `formatElapsed()` escalates: `(5s)` → `(2m 5s)` → `(1h 2m 5s)`. Multiple concurrent tasks show cycling tag: `(1/3 - 2m 5s)`. Both ChatView and fallback PromptInput paths share the same format. +**VirtualList is the scrollable rendering primitive** +`VirtualList` manages scroll state, ID-keyed height caching, screen-to-item mapping, and scrollbar rendering. ChatView builds `VirtualListItem[]` (banner + separator + feed items) each render. Items array is cheap — just references, heights cached by ID. -### Filter by task, not by agent -When suppressing events for background/system tasks, filter at the task level (via flags on `TaskAssignment`/`TaskResult`), never at the agent level. Agent-level suppression (`silentAgents`) blocks ALL events for that agent — including concurrent user tasks. The `system` flag on events is the correct pattern. `silentAgents` is only used for the short-lived defensive retry window. +**Virtualized height caches need explicit invalidation** +`VirtualList` caches geometry by item ID, so any item whose rendered height can change must be invalidated deliberately. The banner (`__banner__`) is the canonical case — invalidate it every render during animation. -### Cross-folder write boundary enforcement -AI teammates must not write to another teammate's folder. Two layers: (1) prompt rule in `adapter.ts` — `### Folder Boundaries (ENFORCED)` section injected for `type: "ai"` only, (2) post-task audit via `auditCrossFolderWrites()` in `cli.ts` — scans `changedFiles` for paths inside `.teammates//`, shows `[revert]`/`[allow]` actions. Allowed: own folder, `_` prefix (shared), `.` prefix (ephemeral), root-level `.teammates/` files. +**Shift every related index in one place** +Any feed insertion that shifts thread ranges must also shift adjacent bookkeeping like activity-line indices and blank-line indices. The `shiftAllContainers` getter is the single coordination point — extend it, never duplicate the shift logic elsewhere. -### Interrupt-and-resume — deferred promise pattern -`/interrupt [teammate] [message]` kills a running agent and resumes with context. `spawnAndProxy` uses a deferred promise — `done` is shared between `executeTask` (normal await) and `killAgent` (SIGTERM → 5s → SIGKILL, then await `done`). `activeProcesses` map tracks `{ child, done, debugFile }` per teammate. Resume prompt wraps the parsed conversation log in `` and goes through normal `buildTeammatePrompt` wrapping. The `killAgent?()` method is optional on `AgentAdapter`. +**Rendered actions need unique IDs** +Every clickable action needs its own ID plus a side lookup for payload state. Reused IDs make later clicks act on the newest handler state instead of the rendered item. -### Log parser extracts structure, not content -`log-parser.ts` parses Claude debug logs, Codex JSONL, and raw agent output into a timeline of actions (Read, Write, Search, etc.). `formatLogTimeline()` groups 4+ consecutive same-action entries to collapse bulk operations. `buildConversationLog()` orchestrates parsing with token budget truncation. Extracts file paths and search queries, NOT full file contents — keeps resume prompts compact. +**Progress belongs behind a tiny API** +Keep progress behind `startTask()`, `stopTask()`, and `showNotification()`. `StatusTracker` owns animation, truncation, and terminal-width budgeting; callers should not manage lifecycle details themselves. Never create custom spinners that duplicate StatusTracker's job. -### ChatView performance — cached heights + coalesced refresh -Feed line height cache (`_feedHeightCache[]`) stores measured heights per line, invalidated on width change or content mutation. Prevents O(N) re-measurement on every render frame. `app.scheduleRefresh()` coalesces rapid progress updates into a single render via `setImmediate`. Spinner interval is 200ms (not 80ms) to avoid saturating the event loop under concurrent task load. +**Terminal width must be measured, not assumed** +Use `process.stdout.columns || 80` for layout math. Hardcoded `80` causes suffix clipping and stray characters on narrower terminals. When the elapsed-time suffix won't fit, omit it entirely. -### /script command — user-defined reusable scripts -Scripts stored under the user's twin folder (`.teammates//scripts/`). Three modes: `/script list`, `/script run `, `/script ` (create + run new). The coding agent always handles `/script` tasks — routes to `selfName`. +**charWidth must respect Windows Terminal wide overrides** +`charWidth()` in `symbol.ts` has three tiers: (1) standard CJK/fullwidth → width 2, (2) `Emoji_Presentation=Yes` characters → width 2, (3) 17 Windows Terminal wide overrides (e.g. ℹ ★ ☆ ♠ ♣ ♥ ♦ ⚐ ⚑ ⚙ ⚠ ✔ ✖ ➜ ➤ ▶ ⏱) → width 2. Characters with text presentation (✓ ✂ © ®) remain width 1. Getting this wrong causes "tracer" ghost characters from the continuation cell. -### Clean dist before rebuild -After modifying any TypeScript source, run `rm -rf dist && npm run build` in the package. Stale artifacts in dist/ can mask compile errors. Running CLI must be restarted after rebuilds — Node.js caches modules at startup. +## Threads -### Lint after every build -After every build, run `npx biome check --write --unsafe` on changed files. If fixes are applied, rebuild to verify they compile cleanly. This is mandatory — lint errors should never be left behind. +**Thread container must exist before placeholders** +`renderThreadHeader()` creates the `ThreadContainer`. Always call it before `renderTaskPlaceholder()`. Reversing the order causes placeholders to silently not render because the container doesn't exist yet. -### Bump all version references on version bump -When bumping package versions, update ALL references — not just the three package.json files. Also update `cliVersion` in `.teammates/settings.json`. Grep for the old version string to catch any other references. Known sites: `packages/cli/package.json`, `packages/consolonia/package.json`, `packages/recall/package.json`, `.teammates/settings.json`. +**Thread insertion must be non-destructive** +Use `peekInsertPoint()` to inspect where thread content should go and reserve `getInsertPoint()` for the actual write. Reading with the destructive path pushes content past the thread action line. -### Folder naming convention in .teammates/ -No prefix = teammate folder (contains SOUL.md). `_` prefix = shared non-teammate folder, checked in. `.` prefix = local/ephemeral, gitignored. Registry skips `_` and `.` prefixed dirs when scanning for teammates. +**Thread action ownership is fixed** +Thread-level verbs are only `[reply] [copy thread]`, and they live at the bottom of the thread container. Per-item verbs are `[show/hide] [copy]` on the subject line, never between subject and body. -### Wordwheel commands without args execute on Enter -No-arg commands (/exit, /status, /help) execute immediately when selected from the wordwheel dropdown. Enter key handler accepts the highlighted item before readline processes it. Commands with arg placeholders should use single tokens (e.g., `[description]` not multi-word usage strings) so the hint clears after the first typed arg. +**Thread-local content stays in the container** +Anything that belongs to a thread — including handoffs, activity blocks, and replies — must insert through the thread container context. Appending to the global feed breaks thread boundaries and verb placement. -### Emoji spacing convention -All ✔/✖/⚠ emojis get double-space after them for consistent rendering across terminals. Applied globally in cli.ts. +**Container context pattern for scoped insertion** +When a subsystem (handoffs, activity) needs to insert lines within a thread, pass a container context interface (`insertLine()`/`insertActions()`) rather than always appending to the global feed. This keeps thread boundaries intact without coupling the subsystem to `ThreadContainer` internals. -### Persona template system -15 persona templates in `packages/cli/personas/` with YAML frontmatter (persona, alias, tier, description) and SOUL.md body with `` placeholders. `loadPersonas()` reads and sorts by tier. `scaffoldFromPersona()` creates teammate folder. Tier 1 = Core (SWE, PM, QA, DevOps), Tier 2 = Specialized. Wired into both pre-TUI onboarding and `/init pick`. +## Activity Tracking -### Action buttons need unique IDs -Feed action buttons (e.g., `[copy]`, `[revert]`, `[allow]`) must have unique IDs tied to their context. Static IDs cause all buttons to share a single handler — clicking any button executes against the most recent context. Pattern: `--` with a `Map` storing per-ID context. Handler looks up by ID, falls back to latest. +**Activity pipelines are adapter-specific** +Claude activity comes from its debug log (`--debug-file`). Codex and Copilot activity come from tailing their paired JSONL debug log files. All three agents write paired debug files under `.teammates/.tmp/debug/`. The PostToolUse hook system was removed — it never worked because Claude doesn't propagate custom env vars to hook subprocesses. -### Extracted pure functions live in cli-utils.ts -Testable pure functions extracted from cli.ts: `relativeTime`, `wrapLine`, `findAtMention`, `isImagePath`, `cleanResponseBody`, `formatConversationEntry`, `buildConversationContext`, `findSummarizationSplit`, `buildSummarizationPrompt`, `preDispatchCompress`, `compressConversationEntries`. New extractions should follow this pattern — pure logic in cli-utils.ts, wired into cli.ts via imports. +**Activity watchers must start from byte zero** +Each task's debug log file is unique. Start per-task file watchers from byte `0`, not from the current file size. Otherwise, early commands written before the watcher attaches are silently dropped. + +**Codex activity is a multi-shape JSONL stream** +Parse `command_execution`, `file_change`, `exec_command_begin`, `patch_apply_begin`, `web_search_begin`, `mcp_tool_call_begin`, `item.started`, `item.completed`, `response.output_item.added/done`, `custom_tool_call`/`function_call`. Arguments may arrive as objects or stringified JSON. Unwrap PowerShell `-Command "..."` wrappers before classifying. De-dup start/completed pairs and flush the final buffered line on close. + +**Codex activity must watch logFile, not debugFile** +Codex does not support `--debug-file`. The adapter creates and appends to `logFile` during execution. Gate Codex watcher startup on `logFile` existing, not `debugFile`. This is the #1 cause of "parser works but UI shows nothing." + +**Copilot activity parses tool.execution_start events with expanded mappings** +`parseCopilotJsonlLine()` maps `tool.execution_start` events into standard activity labels. Mappings include: `view`→Read, `shell`/`bash`/`powershell`→Bash, `grep`/`search`→Grep, `glob`→Glob, `edit`/`write`/`create`→Edit/Write, `task`/`read_agent`/`write_agent`→Agent, `web_search`→WebSearch, `web_fetch`→WebFetch, `github-mcp-server-*` prefix→Search/Read. `COPILOT_PLUMBING` set filters internal tools (`report_intent`, `store_memory`, etc.). Uses event `timestamp` field for elapsed time. + +**Collapse activity but preserve singles** +Group consecutive research runs of 2+ events into `Exploring (N× Read, ...)`. Merge repeated edits to the same file. Filter out TodoWrite and ToolSearch. Never collapse errors. But preserve a single research event (`Read`, `Grep`) as a first-class line — collapsing one event into `Exploring (1× Read)` hides useful evidence. + +**Activity cleanup must be thorough** +When a task completes or is cancelled, hide all activity display lines and delete all bookkeeping state (buffers, indices, blank lines, shown flags). Stale indices from prior feed insertions are the #1 cause of leftover activity lines. + +## Terminal & Mouse + +**Mouse tracking is pure ANSI — no Win32 SetConsoleMode** +Consolonia uses ANSI DECSET escape sequences only for mouse tracking. Do not call Win32 `SetConsoleMode()` via FFI. The Consolonia creator confirmed ANSI codes are more accurate. Node.js/libuv drops `MOUSE_EVENT` records from `ReadConsoleInputW`, making `ENABLE_MOUSE_INPUT` useless and potentially counterproductive. + +**Six mouse protocols are supported** +Consolonia parses SGR (`ESC [ < ...`), classic xterm (`ESC [ M ...`), and URXVT (`ESC [ Cb;Cx;Cy M` without `<`). UTF-8 mode needs no separate parser (Node.js decodes automatically). SGR-Pixels uses the same wire format as SGR. Mouse enable requests all six DECSET modes: `?1000h`, `?1003h`, `?1005h`, `?1006h`, `?1015h`, `?1016h`. Terminals pick the highest they support. + +**Terminal environment detection tailors init sequences** +`detectTerminal()` in `terminal-env.ts` probes `process.env` and `process.platform` to identify the terminal (Windows Terminal, VS Code, ConEmu, mintty, conhost, tmux, screen, iTerm2, Alacritty, etc.) and returns `TerminalCaps`. `initSequence()`/`restoreSequence()` compose escape strings based on detected caps. Full mouse modes when SGR is supported; minimal `?1000h + ?1003h` fallback otherwise. + +## Architecture + +**cli.ts is decomposed into 12 focused modules** +Original 6815 lines → 1986 lines (-71%). Modules: `status-tracker`, `handoff-manager`, `retro-manager`, `wordwheel`, `service-config`, `thread-manager`, `onboard-flow`, `activity-manager`, `startup-manager`, `commands`, `conversation`, `feed-renderer`. Each receives deps via typed interface with closure-backed getters for shared mutable state. + +**All three agents use the CLI proxy adapter pattern** +Claude, Codex, and Copilot are all `CliProxyAdapter` subclasses with agent-specific presets in `presets.ts`. The Copilot SDK was removed — copilot uses stdin piping in interactive mode (no `-p` flag) to avoid Windows command-line length limits. + +**Adapter presets live outside the base class** +Keep shared preset definitions in `presets.ts` and agent-specific adapters in their own files (`claude.ts`, `codex.ts`, `copilot.ts`). Putting presets inside `cli-proxy.ts` creates circular imports that can leave the base class undefined at extension time. + +**Copilot requires stdin piping, not -p** +`copilot -p ` passes the prompt as a command-line argument, which exceeds Windows' ~32K char limit for large prompts. `copilot -p -` treats `-` as literal text, not stdin. Interactive mode (no `-p`) with stdin piping is the only working path. Use `stdinPrompt: true` on the preset. + +**Codex has no -a (approval) flag** +`codex exec` only supports `-s` (sandbox), not `-a` (approval). Passing `-a never` causes "unexpected argument" errors. Use `-s danger-full-access` for non-interactive mode. + +**Cancellation uses AbortSignal, not adapter killAgent** +`cli.ts` creates an `AbortController` per running task. The signal flows through `TaskAssignment` → orchestrator → adapter → `spawnAndProxy()`. Adapters react to abort by killing the child process (SIGTERM → 5s → SIGKILL). `controller.abort()` is synchronous from the caller's perspective. + +**Cross-folder write boundaries are two-layer** +Layer 1 is the prompt rule in `adapter.ts` for AI teammates. Layer 2 is a post-task audit with `[revert]` and `[allow]` actions. Relying on either layer alone is too weak. + +**Handoffs are fenced blocks first** +Structured handoffs should be fenced `handoff` blocks. Natural-language detection is only an emergency fallback and should not be treated as a normal path. + +**Registry discovery skips special folders** +Inside `.teammates\`, bare names are teammates, `_` prefixes are shared checked-in folders, and `.` prefixes are local ephemeral folders. Discovery logic must ignore `_` and `.` entries when resolving teammates. + +**Human avatars are not teammates** +When importing teammates from another project, skip folders where SOUL.md has `**Type:** human`. Never copy USER.md during import — it is user-specific and gitignored. + +**Debug logging is paired files per task** +Each adapter writes two files under `.teammates/.tmp/debug/`: `--prompt.md` (full prompt sent) and `-.md` (activity/debug log). Non-Claude log files are pre-created at task start and appended incrementally during execution so the pair exists immediately. Claude's log file is passed as `--debug-file` so the agent writes directly. + +**Persona templates are folder-based with alias as canonical name** +Bundled personas live under `packages/cli/personas//` with `SOUL.md` and `WISDOM.md`. The loader only accepts folders whose directory name matches `alias:` in the frontmatter. The frontmatter parser must accept CRLF line endings for Windows compatibility. + +## Build & Ship + +**Clean dist before rebuilding** +Always remove `dist` before `npm run build`. Stale build artifacts hide compile problems and can make a broken source tree look healthy. + +**Lint after every build** +Run Biome with auto-fix after the build, then rebuild if lint changed code. Build-clean-build is the required verification loop, not an optional polish step. + +**Version bumps touch every reference** +When bumping package versions, update all package manifests, `.teammates/settings.json` (`cliVersion`), and grep for any other copies of the old version string. Partial bumps leave the workspace inconsistent. + +**Workspace deps should stay wildcarded** +Use `"*"` for workspace package references. Pinned semver can resolve to registry builds or invalidate newer local workspace packages after a bump. + +**ESM path resolution must be explicit** +Resolve sibling files with `fileURLToPath(new URL(..., import.meta.url))`, never `__dirname`. Path-sensitive startup code should fail loudly or log clearly; silent catches hide broken behavior too long. + +**Spawned stdin needs EOF protection** +Whenever the CLI writes to a child process stdin, attach an error handler that swallows `EPIPE` and `EOF`. Some agents close stdin early and that should not crash the parent. + +**Normalize backslash paths for cross-platform compatibility** +When using `path.basename()` or similar path utilities on paths that may contain Windows backslashes, normalize `\` to `/` first. On Linux, `path.basename()` does not recognize `\` as a separator and returns the entire path string. + +## Process + +**Spec first for major UI shifts** +Write the UI spec before implementing changes that alter layout, action placement, or state ownership. Terminal UI work drifts fast without a written target. + +**Verify before logging** +Do not record a fix until the file is actually written and verified. False "done" entries poison future debugging by sending the next pass after behavior that never shipped. + +**Restart the CLI after rebuilds** +Node.js caches modules at startup. After rebuilding packages, the running CLI still uses old code until it is restarted. diff --git a/.teammates/beacon/memory/2026-03-13.md b/.teammates/beacon/memory/2026-03-13.md index 85f9e6f..9951828 100644 --- a/.teammates/beacon/memory/2026-03-13.md +++ b/.teammates/beacon/memory/2026-03-13.md @@ -1,5 +1,4 @@ --- -version: 0.6.0 type: daily compressed: true --- diff --git a/.teammates/beacon/memory/2026-03-14.md b/.teammates/beacon/memory/2026-03-14.md index 15844a9..2c3dbbe 100644 --- a/.teammates/beacon/memory/2026-03-14.md +++ b/.teammates/beacon/memory/2026-03-14.md @@ -1,5 +1,4 @@ --- -version: 0.6.0 type: daily compressed: true --- diff --git a/.teammates/beacon/memory/2026-03-15.md b/.teammates/beacon/memory/2026-03-15.md index ee35724..e2564e4 100644 --- a/.teammates/beacon/memory/2026-03-15.md +++ b/.teammates/beacon/memory/2026-03-15.md @@ -1,5 +1,4 @@ --- -version: 0.6.0 type: daily compressed: true --- diff --git a/.teammates/beacon/memory/2026-03-16.md b/.teammates/beacon/memory/2026-03-16.md index a6b5540..d06e2d6 100644 --- a/.teammates/beacon/memory/2026-03-16.md +++ b/.teammates/beacon/memory/2026-03-16.md @@ -1,5 +1,4 @@ --- -version: 0.6.0 type: daily compressed: true --- diff --git a/.teammates/beacon/memory/2026-03-17.md b/.teammates/beacon/memory/2026-03-17.md index 0c420cf..11aed0b 100644 --- a/.teammates/beacon/memory/2026-03-17.md +++ b/.teammates/beacon/memory/2026-03-17.md @@ -1,5 +1,4 @@ --- -version: 0.6.0 type: daily compressed: true --- @@ -38,9 +37,6 @@ Added `.replace(/^@/, "")` to cmdCompact arg parsing. Files: cli.ts ## Task: Fix @everyone accent color in input box Added "everyone" to both `validNames` sets in colorize callbacks. Files: cli.ts -## Task: Add wisdom distillation to /compact -Added `buildWisdomPrompt()` to compact.ts. After episodic compaction, queues agent task to rewrite WISDOM.md from typed memories + daily logs. Files: compact.ts, cli.ts - ## Task: Bundle recall as dependency + automatic pre-task context Added `@teammates/recall` as cli dependency. `queryRecallContext()` runs pre-task with `skipSync: true`. `syncRecallIndex()` replaces subprocess calls. Removed watch process. Recall section between WISDOM and daily logs. Files: cli/package.json, adapter.ts, cli-proxy.ts, copilot.ts, cli.ts, index.ts diff --git a/.teammates/beacon/memory/2026-03-18.md b/.teammates/beacon/memory/2026-03-18.md index 4d35b4a..d6adace 100644 --- a/.teammates/beacon/memory/2026-03-18.md +++ b/.teammates/beacon/memory/2026-03-18.md @@ -1,5 +1,4 @@ --- -version: 0.6.0 type: daily compressed: true --- diff --git a/.teammates/beacon/memory/2026-03-19.md b/.teammates/beacon/memory/2026-03-19.md index cf8330c..eff817b 100644 --- a/.teammates/beacon/memory/2026-03-19.md +++ b/.teammates/beacon/memory/2026-03-19.md @@ -1,5 +1,4 @@ --- -version: 0.6.0 type: daily compressed: true --- diff --git a/.teammates/beacon/memory/2026-03-20.md b/.teammates/beacon/memory/2026-03-20.md index 2661f29..9b41853 100644 --- a/.teammates/beacon/memory/2026-03-20.md +++ b/.teammates/beacon/memory/2026-03-20.md @@ -1,5 +1,4 @@ --- -version: 0.6.0 type: daily compressed: true --- diff --git a/.teammates/beacon/memory/2026-03-21.md b/.teammates/beacon/memory/2026-03-21.md index b83ba4f..2f951fe 100644 --- a/.teammates/beacon/memory/2026-03-21.md +++ b/.teammates/beacon/memory/2026-03-21.md @@ -1,5 +1,4 @@ --- -version: 0.6.0 type: daily compressed: true --- diff --git a/.teammates/beacon/memory/2026-03-22.md b/.teammates/beacon/memory/2026-03-22.md index 3cd716c..56f3559 100644 --- a/.teammates/beacon/memory/2026-03-22.md +++ b/.teammates/beacon/memory/2026-03-22.md @@ -1,5 +1,4 @@ --- -version: 0.6.0 type: daily compressed: true --- diff --git a/.teammates/beacon/memory/2026-03-23.md b/.teammates/beacon/memory/2026-03-23.md index 08cecdf..98eb6c9 100644 --- a/.teammates/beacon/memory/2026-03-23.md +++ b/.teammates/beacon/memory/2026-03-23.md @@ -1,15 +1,6 @@ --- -version: 0.6.0 type: daily compressed: true --- # 2026-03-23 -## Task: Auto-compaction for daily log budget overflow -`autoCompactForBudget()` compacts oldest weeks first when total exceeds budget. Current week gets `partial: true` frontmatter. `compactDailies()` merges partial weeklies on next run. Hooked into cli-proxy + copilot adapters pre-prompt. Exported `DAILY_LOG_BUDGET_TOKENS`. Key: today excluded, partial detection via frontmatter flag, existing entries take precedence on dedup. 12 new tests. Files: compact.ts, adapter.ts, cli-proxy.ts, copilot.ts, index.ts, compact.test.ts - -## Task: Move startup compaction progress to progress bar -Added `silent` parameter to `runCompact()`. Silent mode suppresses status-only messages from feed (actual work + errors always show). `startupMaintenance()` uses setProgress instead of feedLine, calls runCompact with silent=true. Files: cli.ts - -## Task: Wire auto-compact into runCompact + simplify startup loop -`runCompact()` now calls `autoCompactForBudget()` before `compactEpisodic()`. Removed stale-log pre-check from startupMaintenance — loops all teammates unconditionally. Files: cli.ts diff --git a/.teammates/beacon/memory/2026-03-25.md b/.teammates/beacon/memory/2026-03-25.md index fc1096c..3be703a 100644 --- a/.teammates/beacon/memory/2026-03-25.md +++ b/.teammates/beacon/memory/2026-03-25.md @@ -1,19 +1,12 @@ --- -version: 0.6.0 type: daily compressed: true --- # 2026-03-25 -## Task: Scrub system tasks from daily logs -Removed 6 system task entries from 4 daily logs (03-17, 03-20, 03-23, 03-25) and 3 entries from 2 weekly summaries (W12, W13). Saved feedback memory to prevent future logging. Files: 4 daily logs, 2 weekly summaries, feedback_no_system_tasks_in_logs.md - ## Task: Non-blocking system tasks + debug enhancements (4 changes) (1) System task lane: `isSystemTask()` helper, `systemActive` map, `kickDrain()` extracts system tasks first, `runSystemTask()` runs independently. (2) System tasks suppress feed output (errors only with `(system)` label). (3) `/debug` now accepts ` ` — focus text narrows analysis. (4) Full prompt in debug logs via `fullPrompt` on TaskResult. Key: system tasks use unique `sys--` IDs for concurrent execution. Files: types.ts, cli.ts, cli-proxy.ts, copilot.ts -## Task: Gate runCompact progress on silent flag -Gated 4 progress bar sites in `runCompact()` on `!silent`. Files: cli.ts - ## Task: System tasks fully background + standardized progress format Removed system tasks from progress bar and /status. Added `startTime` to activeTasks. `formatElapsed()`: `(5s)` → `(2m 5s)` → `(1h 2m 5s)`. Format targets 80 chars. Files: cli.ts @@ -32,9 +25,6 @@ All 3 packages 0.5.1 → 0.5.2. Files: 3 package.json ## Task: Changelog v0.5.0→v0.5.2 Compiled from daily logs + git diff. Identified cleanup targets in scribe/pipeline logs. -## Task: Scrub system tasks from scribe and pipeline logs -Removed entries from scribe (03-23, W12, W13) and pipeline (03-23) daily/weekly logs. Files: 4 files across scribe/pipeline - ## Task: Fix conversation history — store full message bodies Root cause: storeResult stored `result.summary` (subject only). Now stores full cleaned `rawOutput` (protocol artifacts stripped). `buildConversationContext()` formats multi-line entries. 24k budget + auto-summarization handles increased size. Files: cli.ts diff --git a/.teammates/beacon/memory/2026-03-27.md b/.teammates/beacon/memory/2026-03-27.md index 73b5824..7d5376e 100644 --- a/.teammates/beacon/memory/2026-03-27.md +++ b/.teammates/beacon/memory/2026-03-27.md @@ -1,5 +1,4 @@ --- -version: 0.6.0 type: daily compressed: true --- diff --git a/.teammates/beacon/memory/2026-03-28.md b/.teammates/beacon/memory/2026-03-28.md index 424c8ad..38e6370 100644 --- a/.teammates/beacon/memory/2026-03-28.md +++ b/.teammates/beacon/memory/2026-03-28.md @@ -1,36 +1,65 @@ --- -version: 0.6.3 type: daily +compressed: true --- # 2026-03-28 ## Task: Fix `this.app.scheduleRefresh is not a function` error +Bug: pinned workspace deps `"@teammates/consolonia": "0.6.0"` resolved to registry version missing `scheduleRefresh()`. Fix: changed both workspace deps to `"*"`. Files: cli/package.json -Bug: all teammates failed with `this.app.scheduleRefresh is not a function` during @everyone standup dispatch. +## Task: Version bump to 0.6.3 +All 3 packages 0.6.2 → 0.6.3. Updated cliVersion in settings.json. Clean build + lint + 924 tests pass. Files: 3 package.json, settings.json -### Root cause -`packages/cli/package.json` had pinned `"@teammates/consolonia": "0.6.0"` and `"@teammates/recall": "0.6.0"`, but local packages were at `0.6.2`. npm workspace resolution marked these as **invalid**, potentially resolving to the registry's 0.6.0 which lacks `scheduleRefresh()` (added in the 0.6.2 cycle). +## Task: Threaded task view — Phase 1 (data model + thread tracking) +`TaskThread`/`ThreadEntry` interfaces, `threadId` on all `QueueEntry` variants, `createThread`/`getThread`/`appendThreadEntry` methods, `#id` prefix parsing, handoff threadId propagation. Thread IDs are session-scoped auto-incrementing integers. Files: types.ts, cli.ts, index.ts -### Fix -Changed both workspace deps to `"*"` so they always resolve to the local workspace version regardless of version bumps. Ran `npm install` to fix resolution, rebuilt all 3 packages. +## Task: Threaded task view — Phase 2 (feed rendering) +ChatView: `insertToFeed`/`insertStyledToFeed`/`insertActionList`, `_shiftFeedIndices`, visibility API for collapse. CLI: thread feed ranges, working placeholders, `displayFlatResult`/`displayThreadedResult` split, collapse toggles. Files: chat-view.ts, cli.ts -### Key decisions -- Used `"*"` instead of `"0.6.2"` to prevent this from recurring on future version bumps -- Workspace protocol (`workspace:*`) would be better but requires npm 9+ support — `"*"` is simpler and works +## Task: Threaded task view — Phase 3 (routing + context) +`buildThreadContext()` for thread-local conversation context, auto-focus routing via `focusedThreadId`, `#id` wordwheel completion, thread-aware `/status`. Thread context fully replaces global when threadId is set. Files: cli-utils.ts, cli.ts, index.ts -### Files changed -- `packages/cli/package.json` — workspace dep versions `0.6.0` → `*` +## Task: Fix agents producing lazy responses from stale session/log state +3 prompt guardrails in adapter.ts: (1) "Task completed" not valid body, (2) prior session entries ≠ user received output, (3) only log work from THIS turn. Files: adapter.ts -## Task: Version bump to 0.6.3 +## Task: Thread format redesign — in-place rendering +Merged dispatch info into thread header (`#1 → @names`). Working placeholders show `@name - working on task...`. Responses render in-place at placeholder position via `_threadInsertAt` override. Files: cli.ts + +## Task: Thread dispatch line — merge into user message block +Changed `renderThreadHeader` from standalone action to `feedUserLine` with dark bg (matches user message). Files: cli.ts + +## Task: Thread rendering polish — 5 visual fixes +(1) Blank line between user msg and thread header. (2) Placeholder format `@name: working...` + `completeWorkingPlaceholder()`. (3) Blank line after each reply. (4) Removed `#id` input seeding. (5) Response ordering fix. Files: cli.ts + +## Task: Fix blank line between dispatch line and working placeholders +Changed `feedLine()` to `threadFeedLine(tid, "")` in all 3 `queueTask` paths — ensures blank line stays within thread range. Files: cli.ts + +## Task: Thread reorder + smart auto-scroll +Completed responses now insert before remaining working placeholders (first-to-finish at top). `_userScrolledAway` flag in ChatView prevents auto-scroll when user scrolled up. Files: chat-view.ts, cli.ts + +## Task: Fix thread rendering — #id in header + endIdx double-increment +(1) Added `#id` prefix to dispatch line. (2) Fixed `endIdx` double-increment in `threadFeedLine`/`threadFeedActionList` — saved `oldEnd` before shift, only manual increment if shift didn't extend range. Files: cli.ts + +## Task: Fix ChatView _shiftFeedIndices off-by-one — response headers hidden +All 3 insert methods called `_shiftFeedIndices(clamped + 1, 1)` but should be `clamped` — off-by-one corrupted hidden set, height cache, hover state. Fixed all 3 sites. Files: chat-view.ts + +## Task: Thread view redesign — Phase 1 (ThreadContainer class) +New `ThreadContainer` class (~230 LOC) encapsulating per-thread feed-line index management. Replaced 5 scattered maps + 10+ methods in cli.ts. Files: thread-container.ts (NEW), cli.ts, index.ts + +## Task: Thread view redesign — Phase 2 (verb relocation) +Per-item `[reply]`/`[copy]` → inline subject-line actions (`[show/hide] [copy]`). Thread-level `[reply] [copy thread]` at container bottom. `insertThreadActions` on ThreadContainer. Files: thread-container.ts, cli.ts + +## Task: Thread view redesign — Phase 3 (input routing update) +`@mention` breaks focus + creates new thread. Auto-focus fallback uses `focusedAt` timestamp. `updateFooterHint()` shows `replying to #N`. Files: cli.ts + +## Task: Fix [reply] verb not working in threaded task view +(1) `getInsertPoint()` fallback to `endIdx` was past `replyActionIdx` — now checks `replyActionIdx` first. (2) `renderWorkingPlaceholder` moved outside `threadId == null` guard. Files: thread-container.ts, cli.ts -Bumped all 3 packages from 0.6.2 → 0.6.3. Updated `cliVersion` in settings.json. Clean build + lint (64 auto-fixes applied) + rebuild + all tests pass (561 consolonia + 94 recall + 269 cli = 924 total). +## Task: Fix [reply] verb + [show/hide] toggle display +(1) `addPlaceholder()` now inserts before `replyActionIdx`. (2) New `updateActionList()` on ChatView swaps `[show]`/`[hide]` text dynamically. Files: chat-view.ts, thread-container.ts, cli.ts -### Key decisions -- Grepped codebase for old version string to catch all references -- Saved feedback memory to always bump all version references in future +## Task: Fix thread reply rendering — 4 issues +(1) `renderThreadReply()` for user messages inside threads. (2) Dispatch line for replies. (3) `hideThreadActions()`/`showThreadActions()` during work. (4) Thread verbs position fix. Files: thread-container.ts, cli.ts -### Files changed -- `packages/cli/package.json` — 0.6.2 → 0.6.3 -- `packages/consolonia/package.json` — 0.6.2 → 0.6.3 -- `packages/recall/package.json` — 0.6.2 → 0.6.3 -- `.teammates/settings.json` — cliVersion 0.6.2 → 0.6.3 +## Task: Version bump to 0.7.0 +All 3 packages 0.6.3 → 0.7.0. Updated cliVersion in settings.json. Files: 3 package.json, settings.json diff --git a/.teammates/beacon/memory/2026-03-29.md b/.teammates/beacon/memory/2026-03-29.md new file mode 100644 index 0000000..46ccd9f --- /dev/null +++ b/.teammates/beacon/memory/2026-03-29.md @@ -0,0 +1,1002 @@ +--- +type: daily +compressed: true +--- +# 2026-03-29 + +## Task: Fix _shiftFeedIndices off-by-one — actually apply the fix +All three ChatView insert methods still had `clamped + 1` instead of `clamped` — logged as fixed on 03-28 but never committed. Applied the fix to all 3 sites. Files: chat-view.ts + +## Task: Fix [reply] verb — copy #id into input box +Added `chatView.inputValue = #${tid}` to the `thread-reply-*` action handler. Files: cli.ts + +## Task: Fix [reply] [copy thread] position + reply indentation +(1) Added `peekInsertPoint()` to ThreadContainer — non-destructive read vs `getInsertPoint()` which auto-increments. (2) Added 4-space indent to all continuation/wrapped lines in `renderThreadReply()`. Files: thread-container.ts, cli.ts + +## Task: Extract modules from cli.ts — Phase 1 +Extracted 5 modules (6815 → 5549 lines): `status-tracker.ts`, `handoff-manager.ts`, `retro-manager.ts`, `wordwheel.ts`, `service-config.ts`. Each receives deps via typed interface. Closure-based getters bridge private state. Files: 5 new modules, cli.ts, index.ts + +## Task: Extract modules from cli.ts — Phase 2 +Extracted 2 more modules (5549 → 4159 lines, -39% total): `thread-manager.ts` (579 lines), `onboard-flow.ts` (1089 lines). Slash commands NOT extracted — too entangled with cli.ts private state. Files: 2 new modules, cli.ts, index.ts + +## Task: Create migration guide for version upgrades +New `migrations.ts` module with `Migration` interface, `semverLessThan()`, `getMigrationsForUpgrade()`. Two migrations: 0.5→0.6 (compress logs), 0.6→0.7 (update frontmatter). Startup loop runs programmatic + agent migrations in order. Files: migrations.ts (NEW), cli.ts, adapter.ts, compact.ts, index.ts + +## Task: Simplify migrations to MIGRATIONS.md +Replaced typed Migration system with plain markdown file. `buildMigrationPrompt()` parses sections, filters by version, one agent task per teammate. Files: MIGRATIONS.md (NEW), migrations.ts, cli.ts, index.ts + +## Task: Migration progress indicator + interruption guard +`setProgress("Upgrading to v0.7.0...")` during migration. `commitVersionUpdate()` only fires when all migrations complete — interrupted CLI re-runs on next startup. Files: cli.ts + +## Task: Move MIGRATIONS.md to CLI package +Moved from `.teammates/` to `packages/cli/`. Resolves via `import.meta.url` (ESM). Added to `files` in package.json. Files: MIGRATIONS.md, migrations.ts, cli.ts, package.json + +## Task: Fix migration not triggering — __dirname in ESM +`__dirname` undefined in ESM — `readFileSync` silently caught error, skipping all migrations. Fixed with `fileURLToPath(new URL("../MIGRATIONS.md", import.meta.url))`. Files: migrations.ts + +## Task: Fix thread reply indentation + header spacing +Thread reply indent 4→2 chars. Header double space → single space. Files: thread-manager.ts + +## Task: Remove @ prefix from thread names + fix item copy +Removed `@` prefix from all thread rendering (headers, dispatch, placeholders, subjects, clipboard). Item `[copy]` now includes `teammate: subject` header. Files: thread-manager.ts + +## Task: Fix migration progress to use standard StatusTracker +User feedback: migration spinner was custom code duplicating StatusTracker's job. Removed custom spinner (fields, constant, methods). New `startMigrationProgress()` adds a synthetic `activeTasks` entry with teammate="teammates", then calls `startStatusAnimation()`. Now renders as standard `⠋ teammates... Upgrading to v0.7.0... (5s)` format, rotating with any concurrent tasks. Files: cli.ts + +## Task: Remove stuck "Upgraded" feed lines after migration +Two permanent `feedLine()` calls left "✔ Updated from v0.5.0 → v0.7.0" and "✔ Upgraded to v0.7.0" stuck at the bottom of the feed. Removed both — the progress spinner already communicates the upgrade, and the version shows in the banner. Removed: `feedLine` in `commitVersionUpdate()` (line 3658) and migration completion handler (line 1077). Files: cli.ts + +## Task: Redesign progress API — startTask/stopTask/showNotification +Rewrote StatusTracker with clean 3-method public API: `startTask(id, teammate, description)`, `stopTask(id)`, `showNotification(content: StyledLine)`. Tasks rotate with spinner + elapsed time. Notifications are one-shot styled messages that show once then auto-purge on next rotation. Animation lifecycle is fully private — callers never manage start/stop. Updated all 15+ call sites in cli.ts. Removed 6 wrapper methods. Clipboard and compact now use `showNotification()` instead of manual setProgress/setTimeout chains. Files: status-tracker.ts, cli.ts + +## Task: Fix handoffs rendering outside thread container +Handoff boxes were appended to the global feed via `feedLine()`, placing them AFTER the thread container's `[reply] [copy thread]` verbs. Root cause: `HandoffManager.renderHandoffs()` had no concept of thread containers — it always used global feed append. Fix: added `HandoffContainerCtx` interface with `insertLine()`/`insertActions()` methods. When `containerCtx` is provided, handoff lines are inserted via the container (staying within the thread range) instead of appended globally. ThreadManager now creates and passes a `containerCtx` wrapping the thread's `ThreadContainer`. Files: handoff-manager.ts, thread-manager.ts, cli.ts + +## Task: Fix system tasks polluting daily logs (wisdom distillation, compaction, etc.) +Root cause: `buildTeammatePrompt()` always included memory update instructions telling agents to write daily logs — even for system tasks like wisdom distillation. The `system` flag on `TaskAssignment` was propagated to event handlers but never passed to the prompt builder. Fix: added `system?: boolean` option to `buildTeammatePrompt()` and the `AgentAdapter.executeTask()` interface. When `system` is true, the Memory Updates section tells the agent "Do NOT update daily logs, typed memories, or WISDOM.md." Threaded through orchestrator → all 3 adapters (cli-proxy, copilot, echo). Files: adapter.ts, orchestrator.ts, cli-proxy.ts, copilot.ts, echo.ts + +## Task: Add real-time activity tracking + [show activity] [cancel] verbs +New feature: working placeholders now show `[show activity]` and `[cancel]` clickable verbs. Activity events are parsed from Claude's debug log in real-time via file polling. The `[show activity]` toggle inserts timestamped `MM:SS Tool detail` lines below the placeholder — updates live as the agent works. `[cancel]` kills the running agent process. + +### Architecture +1. **activity-watcher.ts** (NEW) — `parseClaudeActivity()` extracts tool use events from Claude debug logs (PostToolUse hook lines, tool errors, file write events). `watchDebugLog()` polls the file and calls a callback with new events. `formatActivityTime()` formats elapsed time as `MM:SS`. +2. **ActivityEvent type** in types.ts — `{ elapsedMs, tool, detail?, isError? }` +3. **onActivity callback** on `TaskAssignment` — threaded through orchestrator → adapter → cli-proxy's `spawnAndProxy()`. Watcher starts after process spawn, stops on process close. +4. **Working placeholder as action list** — changed `ThreadContainer.addPlaceholder()` from `insertStyledToFeed` to `insertActionList` with `[show activity]` and `[cancel]` verbs. Added `getPlaceholderIndex()` public method. +5. **Activity buffer in cli.ts** — `_activityBuffers` (events per teammate), `_activityShown` (toggle state), `_activityLineIndices` (feed line indices for hiding), `_activityThreadIds`. Activity lines inserted via `insertStyledToFeed` + `shiftAllContainers`. +6. **Action handlers** for `activity-*` (toggle show/hide) and `cancel-*` (kill agent) in the `chatView.on("action")` handler. + +### Key decisions +- File polling via `fs.watchFile` (1s interval) for Windows reliability +- Only tracks "work" tools (Read, Write, Edit, Bash, Grep, Glob, Search, Agent, WebFetch, WebSearch) — filters out settings, config, hooks, autocompact noise +- Activity line format: ` MM:SS Tool detail` with dimmed text, errors highlighted +- Cancel uses existing `killAgent()` on the adapter (SIGTERM → SIGKILL escalation) +- Started with Claude only — Codex can be added later via JSONL stdout parsing + +### Files changed +- activity-watcher.ts (NEW ~150 lines) +- types.ts (ActivityEvent interface, onActivity on TaskAssignment) +- adapter.ts (onActivity in executeTask options) +- orchestrator.ts (thread onActivity through to adapter) +- cli-proxy.ts (start/stop watcher in spawnAndProxy) +- echo.ts, copilot.ts (accept broader options type) +- thread-container.ts (addPlaceholder → action list, getPlaceholderIndex) +- thread-manager.ts (placeholder with [show activity] [cancel] verbs) +- cli.ts (activity state, handleActivityEvents, toggleActivity, cancelTask, action handlers) +- index.ts (export new module + ActivityEvent type) + +## Task: Fix 3 activity tracking bugs +Three issues reported by user: (1) No visual feedback when clicking [show activity] — added `insertActivityHeader()` that inserts an "Activity" header line in accent color immediately on first toggle. (2) Missing tool detail for Read/Edit — rewrote `parseClaudeActivity()` to merge "written atomically"/"Renaming" file paths into the subsequent PostToolUse event, eliminating duplicate Write+Edit entries and showing filenames like `Edit WISDOM.md`. Read still can't show file paths (Claude debug log doesn't log them). (3) Activity lines persisted after teammate responds — added `cleanupActivityLines()` that hides all activity feed lines before deleting state. Called from both task completion and cancel paths. Files: activity-watcher.ts, cli.ts + +## Task: Fix activity tracking — add tool details via PostToolUse hook +Root cause: Claude's debug log only contains tool names (`PostToolUse with query: Read`), never tool parameters. Activity lines showed `00:07 Read` with no file path. + +### Solution: PostToolUse hook system +1. **`scripts/activity-hook.mjs`** (NEW) — Node.js hook script that Claude fires after every tool call. Reads JSON from stdin (`{tool_name, tool_input}`), extracts relevant detail (file_path for Read/Edit/Write, command for Bash, pattern for Grep/Glob), appends one-line entry to `$TEAMMATES_ACTIVITY_LOG` file. +2. **`activity-hook.ts`** (NEW) — `ensureActivityHook(projectDir)` auto-installs the hook in `.claude/settings.local.json` at CLI startup. Idempotent — checks for existing hook before adding. +3. **`activity-watcher.ts`** — New `parseActivityLog()` and `watchActivityLog()` for the hook log format. New `watchDebugLogErrors()` watches debug log for errors only. Legacy `parseClaudeActivity()`/`watchDebugLog()` retained for backward compat. +4. **`cli-proxy.ts`** — Sets `TEAMMATES_ACTIVITY_LOG` env var on spawned agent process pointing to per-agent activity file in `.teammates/.tmp/activity/`. Watches both activity log (tool details) and debug log (errors). +5. **`cli.ts`** — Calls `ensureActivityHook()` in `startupMaintenance()`. Cleans old activity log files (>1 day). + +### Expected result +Activity lines will now show: `00:07 Read WISDOM.md`, `00:21 Bash npm run build`, `00:29 Grep /pattern/` instead of bare tool names. + +### Files changed +- scripts/activity-hook.mjs (NEW) +- activity-hook.ts (NEW) +- activity-watcher.ts (rewritten — hook log parser + error-only debug parser + legacy compat) +- cli-proxy.ts (env var + dual watcher setup) +- cli.ts (hook install at startup + activity dir cleanup) +- index.ts (new exports) + +## Task: Fix activity lines persisting after task completion + add trailing blank line +Root cause: `shiftAllContainers` shifted thread container and handoff indices but NOT `_activityLineIndices`. When other feed inserts happened, stored activity indices went stale — `cleanupActivityLines` hid wrong lines, leaving real activity lines visible. Fix: wrapped `shiftAllContainers` getter to also shift all `_activityLineIndices` and `_activityBlankIdx` entries. Removed manual same-teammate index shift in `insertActivityLines` (would double-shift). Also added trailing blank line after activity block: new `_activityBlankIdx` map tracks one blank line per teammate, inserted in `insertActivityHeader`, toggled in `toggleActivity`, cleaned in `cleanupActivityLines`. Files: cli.ts + +## Task: Fix activity tracking not writing anything — add debug log fallback +Root cause: `cli-proxy.ts` only watched the activity hook log (`watchActivityLog`) and debug log errors (`watchDebugLogErrors`). The PostToolUse hook script (`activity-hook.mjs`) doesn't fire — Claude Code doesn't propagate `TEAMMATES_ACTIVITY_LOG` env var to hook subprocesses. Debug log has 45+ `PostToolUse` entries per task, but the legacy full parser (`watchDebugLog`) was never wired up as a fallback. Fix: added `watchDebugLog` as a fallback alongside the hook watcher. Dedup wrapper: if the hook produces events, suppress the legacy parser to avoid duplicates. Three watchers now: (1) hook log for rich detail, (2) legacy debug log for tool names when hook doesn't fire, (3) debug log errors. Files: cli-proxy.ts + +## Task: Chat widget redesign — Phase 1 (FeedStore) +Implemented Phase 1 of the widget model redesign spec (F-widget-model-redesign.md). Created `FeedStore` class that replaces the five parallel index-keyed data structures in ChatView with a single identity-based item collection. + +### What was done +1. **New `feed-store.ts`** (~110 lines) — `FeedItem` interface with `id`, `content`, `actions`, `hidden`, `cachedHeight` fields. `FeedStore` class with `push()`, `insert()`, `get()`, `at()`, `indexOf()`, `clear()`, `invalidateAllHeights()`, `hasActions` getter. +2. **Refactored ChatView** — replaced `_feedLines[]`, `_feedActions` Map, `_hiddenFeedLines` Set, `_feedHeightCache[]`, and `_hoveredAction` index with a single `_store: FeedStore` + `_hoveredItemId: string | null`. +3. **Deleted `_shiftFeedIndices()` entirely** — no more parallel structure shifting on insert. FeedStore handles the single array splice. +4. **Height cache on FeedItem** — went beyond spec (which said keep index-based) by storing `cachedHeight` directly on each `FeedItem`, eliminating the last parallel array. +5. **Moved types** — `FeedActionItem` and `FeedActionEntry` now defined in feed-store.ts, re-exported from chat-view.ts for backward compat. +6. **Updated exports** in consolonia's index.ts — exports `FeedStore`, `FeedItem`, `FeedActionEntry`. + +### Key decisions +- External API stays index-based (callers still pass indices) — no breaking changes for cli.ts or thread-container.ts +- FeedStore is internal to ChatView for Phase 1 — public API unchanged +- Hover tracking switched from index (`_hoveredAction: number`) to ID (`_hoveredItemId: string | null`) +- `cachedHeight` on FeedItem instead of separate array — simpler, no parallel structure + +### Build & test +- TypeScript compiles cleanly (strict mode) — consolonia + cli +- 561 consolonia tests pass, 269 CLI tests pass (830 total) + +### Files changed +- `packages/consolonia/src/widgets/feed-store.ts` (NEW ~110 lines) +- `packages/consolonia/src/widgets/chat-view.ts` (refactored — removed ~50 lines net) +- `packages/consolonia/src/index.ts` (new exports) + +## Task: Collapse activity log — smart event grouping +Activity log was showing every raw tool call individually (30+ noisy lines for a typical task). Added `collapseActivityEvents()` that groups events into a compact summary. + +### Collapsing rules +- Consecutive research tools (Read, Grep, Glob, Search, Agent) → single "Exploring (14× Read, 2× Grep, ...)" line in accent color +- Bash without detail → folded into research; Bash with detail → shown individually +- Consecutive Edit/Write to same file → single "Edit chat-view.ts (×7)" line +- TodoWrite and ToolSearch filtered out entirely (internal plumbing) +- Errors never collapsed + +### Rendering changes +- `handleActivityEvents()` now calls `rerenderActivityLines()` which hides stale display lines and re-inserts fresh collapsed view +- `toggleActivity()` shows collapsed events on first toggle +- Removed TodoWrite from WORK_TOOLS (no longer tracked) + +### Build & test +- TypeScript compiles cleanly (strict mode) +- 269 CLI tests pass + +### Files changed +- `packages/cli/src/activity-watcher.ts` — RESEARCH_TOOLS set, `collapseActivityEvents()` function +- `packages/cli/src/cli.ts` — import, `rerenderActivityLines()`, updated `handleActivityEvents`/`toggleActivity`/`insertActivityLines` +- `packages/cli/src/index.ts` — new export + +## Task: Widget refactor Phase 2 — VirtualList extraction +Extracted the scrollable list rendering from ChatView into a reusable `VirtualList` widget per the F-widget-model-redesign spec. + +### What was done +1. **New `virtual-list.ts`** (~280 lines) — `VirtualListItem` interface, `VirtualListOptions`, `VirtualList` class (extends Control). Manages: scroll state, ID-keyed height cache (Map), screen-to-item mapping, scrollbar rendering/interaction, overlay callback. +2. **FeedStore cleanup** — Removed `cachedHeight` from `FeedItem` interface and `invalidateAllHeights()` from `FeedStore`. Height caching now lives solely in VirtualList. +3. **ChatView refactored** — Removed 16 private fields (scroll offset, scrollbar geometry, screen maps, drag state, feed geometry). Deleted `_renderFeed()` (~175 lines). Added `_buildVirtualListItems()` that assembles banner + separator + FeedStore items into `VirtualListItem[]`. Added `_feedLineAtScreen()` helper for hit-testing. All scroll/invalidation methods delegate to VirtualList. +4. **Selection overlay** — Rendered via `VirtualList.onRenderOverlay` callback (called after items render, before clip pop). +5. **Updated exports** — `VirtualList`, `VirtualListItem`, `VirtualListOptions` exported from consolonia index.ts. + +### Key decisions +- VirtualList is a generic reusable widget (VirtualListItem interface, not coupled to FeedStore) +- FeedItem satisfies VirtualListItem structurally (TypeScript duck typing) — no wrapper needed +- Banner + separator are prefix items with fixed IDs (`__banner__`, `__topsep__`) +- `_feedItemOffset` tracks count of prefix items for feed line index math +- Items array rebuilt each render() call (cheap — just references, heights cached by ID) +- Mouse handling stays in ChatView — VirtualList exposes scrollbar geometry + interaction methods + +### Build & test +- TypeScript compiles cleanly (strict mode) — consolonia + cli +- 561 consolonia tests pass, 269 CLI tests pass (830 total) + +### Files changed +- `packages/consolonia/src/widgets/virtual-list.ts` (NEW ~280 lines) +- `packages/consolonia/src/widgets/feed-store.ts` (removed cachedHeight, invalidateAllHeights) +- `packages/consolonia/src/widgets/chat-view.ts` (major refactor — net ~170 lines removed) +- `packages/consolonia/src/index.ts` (new exports) + +## Task: Fix banner animation not rendering after VirtualList extraction +Root cause: VirtualList caches item heights by ID. The banner (`__banner__`) changes height during animation (adds lines per phase), but the cached height from the first measurement was never invalidated — so the banner appeared frozen/invisible. Fix: added `this._feed.invalidateItem("__banner__")` in `_buildVirtualListItems()` so the banner is re-measured every render. Files: chat-view.ts + +## Task: Change progress message separator from `...` to `-` +Changed progress message format from `... ` to ` - ` at all 3 rendering sites in StatusTracker: TUI width-calc prefix, TUI styled output, and fallback chalk output. Files: status-tracker.ts + +## Task: Fix "Error: write EOF" crash with Codex adapter +Unhandled `EPIPE`/`EOF` error on `child.stdin` when Codex closes its stdin pipe before the prompt write completes. Added `child.stdin.on("error", () => {})` error swallower at all 3 stdin write sites in `cli-proxy.ts`: the `executeTask` spawn path, the `spawnAndProxy` stdin-prompt path, and the interactive stdin forwarding path. Files: cli-proxy.ts + +## Task: Split adapters into separate files per coding agent +User requested each coding agent have its own adapter file instead of all presets in cli-proxy.ts. Created 3 new files: + +### Architecture +- **`presets.ts`** (NEW) — Defines `CLAUDE_PRESET`, `CODEX_PRESET`, `AIDER_PRESET` and aggregates them into `PRESETS`. Separated from cli-proxy.ts to break circular imports (adapter files extend CliProxyAdapter). +- **`claude.ts`** (NEW) — `ClaudeAdapter` extends `CliProxyAdapter` with Claude-specific preset. `ClaudeAdapterOptions` interface. +- **`codex.ts`** (NEW) — `CodexAdapter` extends `CliProxyAdapter` with Codex-specific preset. `CodexAdapterOptions` interface. +- **`copilot.ts`** — Already existed as a separate adapter (SDK-based, not CLI-proxy). +- **`cli-proxy.ts`** — Shared base class (`CliProxyAdapter`), spawn logic, output parsing. Presets removed, re-exported from `presets.ts`. + +### Key decisions +- Preset definitions extracted to `presets.ts` to avoid circular dependency: claude.ts/codex.ts → cli-proxy.ts (for CliProxyAdapter) and cli-proxy.ts → presets.ts (for PRESETS). If presets stayed in cli-proxy.ts, the circular import would cause `CliProxyAdapter` to be undefined when adapter files try to extend it. +- `resolveAdapter()` in cli-args.ts now instantiates `ClaudeAdapter`/`CodexAdapter` directly instead of `new CliProxyAdapter({ preset: name })`. Aider still goes through the generic path. +- All adapter classes + option types exported from index.ts (including CopilotAdapter which wasn't exported before). + +### Files changed +- `adapters/presets.ts` (NEW ~75 lines) +- `adapters/claude.ts` (NEW ~30 lines) +- `adapters/codex.ts` (NEW ~35 lines) +- `adapters/cli-proxy.ts` (removed preset definitions, re-exports from presets.ts) +- `cli-args.ts` (imports ClaudeAdapter/CodexAdapter, direct instantiation) +- `index.ts` (new exports) + +## Task: Fix progress line trailing `)` — dynamic width + suffix omission +Root cause: `renderTaskFrame()` in StatusTracker budgeted task text against hardcoded `80` columns. When actual terminal width was narrower, the suffix (elapsed time tag) would overflow and get clipped, leaving a stray `)`. Fix: (1) replaced `80` with `process.stdout.columns || 80` for real terminal width, (2) added `useSuffix` guard — if remaining budget after prefix+suffix is ≤3 chars, omit the suffix entirely instead of letting it clip. Applied to both TUI (styled) and fallback (chalk) render paths. Files: status-tracker.ts + +## Task: Add Codex live activity parsing from `--json` stdout +Investigated `C:\source\teammates\.teammates\.tmp\debug\scribe-2026-03-29T11-49-03-148Z.md` and confirmed it is only a post-task markdown summary plus stderr, not a live event stream. Verified `codex exec --json` emits JSONL lifecycle events on stdout. Added `parseCodexJsonlLine()` to map Codex `tool_call` events into existing activity labels and hooked `CliProxyAdapter` to parse stdout incrementally during Codex runs. Current mappings normalize `shell_command` to `Read`/`Grep`/`Glob`/`Bash` based on the command text and `apply_patch` to `Edit` using patch targets. Added parser tests. Build + typecheck passed; `vitest` startup failed in this sandbox with Vite `spawn EPERM`. Files: activity-watcher.ts, cli-proxy.ts, activity-watcher.test.ts + +## Task: Assess `codex-tui.log` as a live activity source +Inspected `C:\Users\stevenickman\.codex\log\codex-tui.log` after user pointed to it as the realtime log. Confirmed it contains Codex runtime telemetry (`thread_spawn`, `session_init`, websocket/model startup, shell snapshot warning, shutdown) but no tool-level entries like `tool_call`, `item.completed`, `shell_command`, or `apply_patch`. Decision: detailed `[show activity]` should continue to come from streaming `codex exec --json` stdout; `codex-tui.log` is only useful as an optional coarse lifecycle/error side channel. Files changed: `.teammates/beacon/memory/2026-03-29.md`, `.teammates/beacon/memory/decision_codex_tui_log_not_activity_source.md`, `.teammates/.tmp/sessions/beacon.md` + +## Task: Verify whether the Codex adapter uses JSONL output +Confirmed yes by reading the current adapter code. `CODEX_PRESET` runs `codex exec - ... --json`, `parseOutput()` extracts the last `agent_message` from the JSONL stream, and `CliProxyAdapter` incrementally parses stdout with `parseCodexJsonlLine()` for live activity events. Parser tests already cover the Codex JSONL mapping. Files changed: `.teammates/beacon/memory/2026-03-29.md`, `.teammates/.tmp/sessions/beacon.md` + +## Task: Fix Codex live activity parser to accept begin events +Root cause: Codex live activity parsing was too narrow. The adapter streamed `codex exec --json` stdout, but `parseCodexJsonlLine()` only accepted `item.completed` tool-call events. The installed Codex binary exposes live event names like `exec_command_begin` and `patch_apply_begin`, plus tool arguments may arrive as stringified JSON. Fix: broadened the parser to accept `exec_command_begin`, `patch_apply_begin`, `web_search_begin`, `item.started`, and stringified `item.arguments`. Added a short de-dup window in `cli-proxy.ts` so start/completed pairs do not double-render. Build passed; Vitest still failed to start in this sandbox with Vite `spawn EPERM`. Files: `packages/cli/src/activity-watcher.ts`, `packages/cli/src/adapters/cli-proxy.ts`, `packages/cli/src/activity-watcher.test.ts` + +## Task: Run standup +Prepared and delivered Beacon's standup update for the user's `@everyone run a standup` request. + +### Key decisions +- No code changes were needed. +- No teammate handoffs were needed; this turn was a status report only. + +### Files changed +- `.teammates/.tmp/sessions/beacon.md` +- `.teammates/beacon/memory/2026-03-29.md` + +### Next +- If the user follows up, continue on CLI/Codex live-activity work from today's latest parser changes. + +## Task: Broaden Codex activity parsing beyond begin events +User reported Codex still showed no lines under `[show activity]`. Broadened `parseCodexJsonlLine()` to accept additional Codex JSONL shapes: `response.output_item.added`, `response.output_item.done`, and tool-call item types `custom_tool_call` / `function_call`, with arguments coming from `arguments`, `input`, `payload`, `parameters`, or stringified JSON. Also flush the final buffered Codex stdout line on process close so the last activity event is not lost. + +### Key decisions +- Treat Codex activity as a broader multi-shape stream than the earlier begin-event fix covered. +- Keep event-shape normalization in `activity-watcher.ts`; keep de-dup and trailing-buffer flush in `cli-proxy.ts`. +- Verification used a clean CLI build plus a direct Node import of built `dist/activity-watcher.js` because `vitest` still fails to start in this sandbox with Vite `spawn EPERM`. + +### Files changed +- `packages/cli/src/activity-watcher.ts` +- `packages/cli/src/activity-watcher.test.ts` +- `packages/cli/src/adapters/cli-proxy.ts` +- `.teammates/beacon/memory/decision_codex_activity_requires_begin_events.md` + +### Next +- Have the user restart the CLI so the rebuilt adapter code is loaded, then re-test a Codex task with `[show activity]`. + +## Task: Verify whether the Codex activity fix is actually present +Checked the current source instead of relying on the earlier memory entry. Confirmed the Codex path still contains the intended live-activity logic: `CODEX_PRESET` runs `codex exec - --json`, `parseCodexJsonlLine()` accepts `exec_command_begin`, `patch_apply_begin`, `mcp_tool_call_begin`, `web_search_begin`, `item.started`, `item.completed`, and `response.output_item.{added,done}`, and `CliProxyAdapter` streams stdout through that parser with a short de-dup window plus close-time trailing-buffer flush. + +### Key decisions +- Treat this as "present in source" but not "fully verified shipped" because live verification should include a clean build and a real CLI run. +- Report the current blocker plainly: the CLI package does not compile right now due to unrelated `sessionFile` / `sessionFiles` typing errors, so the Codex activity fix could not be re-validated end-to-end in this turn. + +### Verification +- Read: `packages/cli/src/activity-watcher.ts`, `packages/cli/src/adapters/cli-proxy.ts`, `packages/cli/src/adapters/presets.ts`, `packages/cli/src/activity-watcher.test.ts` +- Ran: `npm run build` in `packages/cli` +- Result: build failed with unrelated TypeScript errors in `adapter.test.ts`, `cli-proxy.ts`, and `copilot.ts` about missing `sessionFile` and `sessionFiles`. + +### Files changed +- `.teammates/.tmp/sessions/beacon.md` +- `.teammates/beacon/memory/2026-03-29.md` + +### Next +- Fix the current CLI compile errors, rebuild, restart the CLI, and run a real Codex task to confirm `[show activity]` renders live lines in practice. + +## Task: Remove session file system entirely +User reported debug/session logs appearing for teammates not being talked to. Root cause: session files (`.teammates/.tmp/sessions/*.md`) were still being created per teammate in `startSession()`, populated by agents during tasks, and the path was injected into every prompt via the `### Session State` section — wasting tokens. + +### What was removed +1. **adapter.ts** — Removed `sessionFile` option from `buildTeammatePrompt()`, removed `getSessionFile?()` from `AgentAdapter` interface, deleted entire `### Session State` prompt section +2. **cli-proxy.ts** — Removed `sessionFiles` Map, `sessionsDir` field, session file creation in `startSession()`, session file passing in `executeTask()`, `getSessionFile()` method, session file cleanup in `destroySession()` +3. **copilot.ts** — Same removals as cli-proxy (sessionFiles, sessionsDir, startSession file creation, executeTask passing, getSessionFile) +4. **adapter.test.ts** — Removed "includes session file when provided" test +5. **cli.ts** — Removed `sessions` from dir-deletion skip list in `cleanOldTempFiles()` +6. Deleted leftover `.teammates/.tmp/sessions/` directory + +### Key decisions +- `.tmp` gitignore guard retained (still needed for debug/activity dirs) — moved to a `_tmpInitialized` flag pattern +- Orchestrator `sessions` Map (session IDs, not file paths) left intact — needed for session continuity +- Clean compile + build verified + +### Files changed +- adapter.ts, adapter.test.ts, cli-proxy.ts, copilot.ts, cli.ts + +## Task: Restructure debug/activity logging — unified prompt + log files per adapter +User wasn't seeing `[show activity]` output for Codex. Restructured all adapters to write two persistent files per task under `.teammates/.tmp/debug/`: +- `--prompt.md` — the full prompt sent to the agent +- `-.md` — adapter-specific activity/debug log + +### Architecture +1. **cli-proxy.ts** — `executeTask()` creates both file paths in `.teammates/.tmp/debug/`, writes prompt immediately, passes logFile to `spawnAndProxy()`. For Claude: logFile passed as `--debug-file` so Claude writes directly. For Codex/others: raw stdout dumped to logFile on process close. Removed old temp-file-in-tmpdir pattern — prompt file now persists in debug dir. +2. **copilot.ts** — Same two-file pattern. Wraps `session.emit` to capture all Copilot SDK events into an activity log array, written to logFile after task completes. +3. **types.ts** — Added `promptFile` and `logFile` fields on `TaskResult`. +4. **cli.ts** — `lastDebugFiles` changed from `Map` to `Map`. `writeDebugEntry()` simplified to just record the adapter-provided paths (no longer writes its own mega debug file). `queueDebugAnalysis()` reads both files and includes both in the analysis prompt sent to `/debug`. + +### Key decisions +- Both files share a `-` base name for easy pairing +- Files stored in `.teammates/.tmp/debug/` (cleaned by startup maintenance after 24h) +- Claude's `--debug-file` pointed directly at the logFile (no copy/rename needed) +- Copilot event capture uses `(session as any).emit` wrapper since CopilotSession type doesn't expose emit + +### Build & test +- TypeScript compiles cleanly (strict mode) +- 277 CLI tests pass + +### Files changed +- types.ts, cli-proxy.ts, copilot.ts, cli.ts + +## Task: Fix wisdom distillation firing on every startup +Root cause: `buildWisdomPrompt()` in `compact.ts` only checked if typed memories or daily logs existed (they always do). It never checked whether wisdom had already been distilled today. Result: 4 agent calls (one per AI teammate) on every CLI startup, burning tokens for no reason. + +Fix: added a `Last compacted: YYYY-MM-DD` date check — if WISDOM.md already shows today's date, return `null` (skip). This matches the pattern already used by `buildDailyCompressionPrompt()` which checks `compressed: true`. + +### Files changed +- compact.ts (added today-check guard in `buildWisdomPrompt()`) + +## Task: Explain .tmp/activity folder creation on startup +User asked why `.teammates/.tmp/activity/` keeps appearing. Traced it to `spawnAndProxy()` in `cli-proxy.ts:479-481` — every agent task spawn creates the directory via `mkdirSync`. The folder was appearing on every startup because the wisdom distillation bug (fixed above) spawned 4 agent processes. With that fix in place, the activity dir will only be created when the user actually assigns a task. + +### Key decisions +- No code changes needed — the wisdom distillation fix resolves the symptom +- The activity dir creation in `spawnAndProxy()` is correct behavior for actual tasks + +## Task: Remove .tmp/activity disk intermediary — pipe activity events in-memory +User feedback: activity log files in `.tmp/activity/` are "on-disk turds" that serve no purpose. The PostToolUse hook (`activity-hook.mjs`) never worked because Claude Code doesn't propagate custom env vars to hook subprocesses. Activity events were always coming from the debug log watcher (Claude) or in-memory stdout parsing (Codex). + +### What was removed +1. **`activity-hook.ts`** — DELETED. Hook installer that wrote to `.claude/settings.local.json`. Dead code since env vars never propagated. +2. **`scripts/activity-hook.mjs`** — DELETED. The PostToolUse hook script itself. +3. **`.tmp/activity/` directory creation** — Removed `mkdirSync(activityDir)` and `activityFile` path construction from `spawnAndProxy()`. +4. **`TEAMMATES_ACTIVITY_LOG` env var** — Removed from spawned process environment. +5. **`watchActivityLog()`** — Removed from `activity-watcher.ts` and all call sites. It watched files that were never written to. +6. **`parseActivityLog()`** — Removed. Only parsed the hook log format. +7. **`ACTIVITY_LINE_RE`** — Removed. Regex for the hook log format. +8. **`ensureActivityHook()` call** — Removed from `startupMaintenance()` in `cli.ts`. +9. **Activity dir cleanup** — Removed from `startupMaintenance()`. +10. **Public exports** — Removed `ensureActivityHook`, `parseActivityLog`, `watchActivityLog` from `index.ts`. +11. **`activityFile` field** — Removed from `SpawnResult` type in `cli-proxy.ts`. +12. **Hook/legacy dedup wrapper** — Removed `hookFired`/`hookCallback`/`legacyCallback` from watcher setup. Now just direct `watchDebugLog()` + `watchDebugLogErrors()`. + +### What remains (correctly) +- **Claude**: `watchDebugLog()` + `watchDebugLogErrors()` — parse Claude's debug log (which Claude writes via `--debug-file`). This file is unavoidable since Claude owns it, and it's already needed for `/debug` analysis. +- **Codex**: In-memory stdout JSONL parsing via `parseCodexJsonlLine()` — events piped directly to `onActivity` callback with no disk I/O. +- **In-memory buffer**: `_activityBuffers` Map in cli.ts stores all events. `collapseActivityEvents()` groups them for display. + +### Key decisions +- Claude's debug log is the only remaining disk dependency for activity, and it was already being created for `/debug` — no new files. +- The "legacy" comment labels in activity-watcher.ts were updated since the debug log parser is now the primary (and only) Claude parser. + +### Files changed +- `activity-hook.ts` (DELETED) +- `scripts/activity-hook.mjs` (DELETED) +- `activity-watcher.ts` (removed hook log parsing, updated comments) +- `adapters/cli-proxy.ts` (removed activity dir/file/env var/hook watcher, simplified watcher setup) +- `adapters/copilot.ts` (comment fix) +- `cli.ts` (removed ensureActivityHook import/call, removed activity dir cleanup) +- `index.ts` (removed dead exports) + +## Task: Shorten working placeholder text +Changed `working on task...` → `working...` at all 4 sites: `cli.ts:3008, 3013` (non-threaded) and `thread-manager.ts:350, 355` (threaded). Clean build + lint cycle completed. Files: cli.ts, thread-manager.ts + +## Task: Make Codex debug log file appear immediately +User reported Codex runs were creating `-prompt.md` but not the paired `.md` while the task was running. Root cause: non-Claude adapters only wrote `logFile` on process close, so the file did not exist during execution and could be missing entirely on early spawn failures. + +### Key decisions +- Pre-create the non-Claude `logFile` in `executeTask()` right after writing the prompt file so the pair exists immediately in `.teammates/.tmp/debug/`. +- Append non-Claude stdout and stderr to the log file incrementally during execution for live inspection. +- Still keep the close-time final write, and append a `[SPAWN ERROR]` marker if process spawn fails before normal shutdown. + +### Build & verification +- `npm run build` in `packages/cli` passed cleanly. +- Source inspection confirmed the new eager-create and incremental append paths in `packages/cli/src/adapters/cli-proxy.ts`. + +### Files changed +- `packages/cli/src/adapters/cli-proxy.ts` + +## Task: Update focused-thread footer hint wording +Changed the focused thread footer hint from `replying to #` to `replying to task #` so the status bar language is clearer while keeping the existing thread ID behavior. + +### Key decisions +- Updated the single source of truth in `ThreadManager.updateFooterHint()` instead of layering a formatting override elsewhere. +- Kept the text lowercase to match the existing footer style. + +### Build & verification +- `npm run build` in `packages/cli` passed cleanly. +- A clean `dist` removal before build was attempted but blocked by the sandbox policy in this session. + +### Files changed +- `packages/cli/src/thread-manager.ts` + +## Task: Fix Codex [show activity] by tailing the debug JSONL file +Read the current `.teammates\.tmp\debug\*.md` Codex log and found the live tool stream was not using the older `tool_call` JSONL shapes. The real file is dominated by `item.started` / `item.completed` entries with `item.type: command_execution`, so the existing parser was dropping them and the feed stayed empty under `[show activity]`. + +### Key decisions +- Switched Codex live activity to follow the same file-watcher model as Claude, but against the paired JSONL debug log file instead of stdout-only parsing. +- Added `watchCodexDebugLog()` with trailing-line buffering so partial JSONL writes are not lost during polling. +- Taught `parseCodexJsonlLine()` to map `item.started` + `item.type: command_execution` into existing `Read` / `Grep` / `Glob` / `Bash` activity labels. +- Normalized wrapped PowerShell commands by unwrapping the `-Command "..."` payload before classification, so entries like `"powershell.exe" -Command "Get-Content ..."` collapse to `Read SOUL.md` instead of a generic Bash line. +- Kept the existing collapse/render path intact so Codex activity still renders as the same compact `Exploring` / `Edit file.ts (×N)` feed format. + +### Verification +- `npm run build` in `packages/cli` passed. +- Direct Node import of built `dist/activity-watcher.js` confirmed a real `command_execution` JSONL line now parses to `[{ tool: "Read", detail: "SOUL.md" }]`. +- Attempted clean `dist` removal before rebuild, but the sandbox blocked the recursive delete command in this session. + +### Files changed +- `packages/cli/src/activity-watcher.ts` +- `packages/cli/src/adapters/cli-proxy.ts` +- `packages/cli/src/activity-watcher.test.ts` +- `packages/cli/src/index.ts` + +### Next +- Restart the CLI so the rebuilt Codex adapter is loaded, then run a Codex task and toggle `[show activity]` to confirm live lines appear in the thread feed. + +## Task: Fix Codex activity watcher gate to use logFile +User reported Codex debug logs were visibly updating on disk while `[show activity]` stayed empty. Root cause was not the parser this time, but the watcher startup gate in `packages/cli/src/adapters/cli-proxy.ts`: watchers only started when `debugFile` existed. That works for Claude because Claude supports `--debug-file`, but Codex does not, so `debugFile` was always `undefined` even though the paired `.teammates\.tmp\debug\*.md` `logFile` was being appended live. + +### Key decisions +- Keep Claude on the existing `debugFile` watcher path. +- Start Codex activity watching from `logFile`, which is the file the adapter actually appends during execution. +- Verify the compiled output too, not just the TypeScript source, so the fix is present in `dist`. + +### Verification +- Rebuilt `packages/cli` successfully with `npm run build`. +- Ran Biome on `packages/cli/src/adapters/cli-proxy.ts`; no changes were needed. +- Confirmed both source and compiled output contain `watchCodexDebugLog(logFile, taskStartTime, onActivity)`. +- Attempted to clean `packages/cli/dist` first, but the sandbox blocked the recursive delete command in this session. + +### Files changed +- `packages/cli/src/adapters/cli-proxy.ts` + +### Next +- Restart the running CLI process so it loads the rebuilt adapter code. +- Re-run a Codex task and toggle `[show activity]`; the live lines should now render from the same debug log file you already see updating. + +## Task: Make /btw skip memory updates +User clarified that `/btw` is an ephemeral side question passed to the coding agent and must not update daily logs or typed memories. Implemented a dedicated `skipMemoryUpdates` flag instead of reusing `system`, so `/btw` keeps normal user-task behavior while suppressing memory-writing instructions in the teammate prompt. + +### Key decisions +- Added a dedicated `skipMemoryUpdates` prompt/assignment flag rather than overloading `system`; `/btw` is not maintenance work and should not inherit system-task semantics. +- Threaded the flag through `TaskAssignment` → `Orchestrator` → all adapters → `buildTeammatePrompt()`. +- Gave ephemeral tasks their own `### Memory Updates` text telling the agent to answer only and not touch memory files. +- Wired the `/btw` dispatch path in `cli.ts` to set `skipMemoryUpdates: entry.type === "btw"`. + +### Build & verification +- `npm run build` in `packages/cli` passed cleanly. +- Attempted a clean `dist` removal first, but the sandbox blocked the recursive delete command in this session. + +### Files changed +- `packages/cli/src/types.ts` +- `packages/cli/src/adapter.ts` +- `packages/cli/src/orchestrator.ts` +- `packages/cli/src/adapters/cli-proxy.ts` +- `packages/cli/src/adapters/copilot.ts` +- `packages/cli/src/adapters/echo.ts` +- `packages/cli/src/cli.ts` +- `packages/cli/src/adapter.test.ts` + +### Next +- Restart the CLI before testing `/btw`, since the running Node process will still have the old adapter code loaded. + +## Task: Extract modules from cli.ts — Phase 3 +Codex hung for 27 minutes on cli.ts (4494 lines) due to repeated `apply_patch` failures — the file is too large for reliable patching. Extracted 2 new modules to continue shrinking it (Phase 1 extracted 5, Phase 2 extracted 2). + +### New modules +1. **`activity-manager.ts`** (~330 lines) — All activity tracking state (5 Maps) + 8 methods: handleActivityEvents, rerenderActivityLines, toggleActivity, insertActivityHeader, insertActivityLines, cleanupActivityLines, updatePlaceholderVerb, cancelRunningTask, initForTask. Typed `ActivityManagerDeps` interface. +2. **`startup-manager.ts`** (~370 lines) — Startup maintenance lifecycle: startupMaintenance, cleanOldTempFiles, checkVersionUpdate, commitVersionUpdate, runCompact. Typed `StartupManagerDeps` interface. + +### Results +- cli.ts: 4494 → 3970 lines (-524, -12%) +- Total reduction across all 3 phases: 6815 → 3970 (-42%) +- TypeScript compiles cleanly (strict mode) +- 279 CLI tests pass, 561 consolonia tests pass (840 total) + +### Key decisions +- Activity state (5 Maps) moved to public readonly fields on ActivityManager so the shiftAllContainers wrapper in cli.ts can still adjust indices during feed insertions. +- Startup manager's `pendingMigrationSyncs` bridged via getter/setter closures since it's a primitive that needs to be shared with cli.ts's runSystemTask migration counter. +- Slash commands NOT extracted — too entangled with cli.ts private state. Each command accesses a different subset of REPL state, making a unified deps interface impractically large. +- Removed unused imports from cli.ts after extraction: ora, readdir, rm, unlink, DAILY_LOG_BUDGET_TOKENS, buildMigrationPrompt, autoCompactForBudget, buildDailyCompressionPrompt, buildWisdomPrompt, compactEpisodic, purgeStaleDailies. + +### Files changed +- `packages/cli/src/activity-manager.ts` (NEW ~330 lines) +- `packages/cli/src/startup-manager.ts` (NEW ~370 lines) +- `packages/cli/src/cli.ts` (refactored — replaced implementations with delegations, removed unused imports) +- `packages/cli/src/index.ts` (new exports: ActivityManager, StartupManager + types) + +## Task: Unify /cancel and /interrupt to work by task-id +Rewrote both commands to use thread IDs (`#N`) as the primary identifier instead of queue position or teammate name. + +### /cancel [task-id] [teammate?] +- Takes a thread ID and optional teammate name +- Without teammate: cancels ALL teammates in the task (both queued and running) +- With teammate: cancels just that teammate +- Each canceled teammate gets a `: canceled` subject line rendered in the thread via `displayCanceledInThread()` +- Kills running agents via `adapter.killAgent()`, removes queued tasks from queue +- Shared `cancelTeammateInThread()` helper handles both paths + +### /interrupt [task-id] [teammate] [message] +- Takes a thread ID, teammate name, and interruption text +- Kills the running agent or removes queued task +- Re-queues with original task text + `\n\nUPDATE:\n` appended +- Cascading: subsequent interruptions keep appending UPDATE sections to the accumulated task text +- Shows new working placeholder in the thread for the re-queued task +- Removed old RESUME_CONTEXT / buildResumePrompt / buildConversationLog approach + +### Cleanup +- Removed `buildResumePrompt()` method (no longer needed) +- Removed `buildConversationLog` import from log-parser.js (no longer used in cli.ts) +- Removed `InterruptState` type from types.ts and index.ts (dead code) +- Biome removed unused `mkdirSync`/`writeFileSync` imports +- Added `cancel` to `TEAMMATE_ARG_POSITIONS` in wordwheel.ts (position 1 — after task-id) +- Changed `interrupt`/`int` from position 0 to position 1 in wordwheel + +### Key decisions +- Thread ID is the task identifier for both commands (matches `#N` UI convention) +- No resume context or conversation log capture — interrupt simply restarts with updated instructions, relying on the agent to check filesystem state +- UPDATE sections cascade naturally because `entry.task` accumulates them + +### Files changed +- `packages/cli/src/cli.ts` — rewrote cmdCancel/cmdInterrupt, added cancelTeammateInThread/getThreadTeammates helpers +- `packages/cli/src/thread-manager.ts` — added `displayCanceledInThread()` method +- `packages/cli/src/wordwheel.ts` — updated TEAMMATE_ARG_POSITIONS +- `packages/cli/src/types.ts` — removed InterruptState +- `packages/cli/src/index.ts` — removed InterruptState export + +## Task: Fix missing working placeholder — renderTaskPlaceholder before container creation +Root cause: In `queueTask()`, both the `@everyone` and `mentioned` paths called `renderTaskPlaceholder()` BEFORE `renderThreadHeader()`. But `renderThreadHeader()` is what creates the `ThreadContainer` — so `renderTaskPlaceholder` found no container and silently returned without rendering anything. The single-match path (no explicit @mention) had the correct order. Fix: moved `renderThreadHeader`/`renderThreadReply` calls before the entry loop in both paths so the container exists when placeholders are rendered. Files: cli.ts + +## Task: Run standup +Prepared and delivered Beacon's standup update for the user's `@everyone do a standup` request. + +### Key decisions +- No code changes were needed. +- No teammate handoffs were needed; this turn was a status report only. + +### Files changed +- `.teammates/beacon/memory/2026-03-29.md` + +### Next +- Continue on CLI thread/orchestration reliability and Codex live activity verification if the user follows up there. + +## Task: Change [cancel] verb to populate input box instead of direct cancel +User reported clicking `[cancel]` wasn't actually cancelling. Changed the action handler to populate the input box with `/cancel # ` instead of calling `cancelTask()` directly. The user just presses enter to confirm. Looks up the entry from both `taskQueue` and `agentActive` to resolve threadId and teammate name. + +### Files changed +- `packages/cli/src/cli.ts` (cancel action handler at ~line 1662) + +## Task: Fix /cancel and /interrupt not accepting #id format +User reported `/cancel` didn't work. Root cause: the `[cancel]` verb populates the input with `/cancel #1 beacon` (with `#` prefix), but `cmdCancel` did `parseInt("#1", 10)` which returns `NaN`. Same bug in `cmdInterrupt`. Fix: added `.replace(/^#/, "")` before `parseInt()` in both commands so `#1` and `1` both parse correctly. + +### Files changed +- `packages/cli/src/cli.ts` (cmdCancel ~line 2691, cmdInterrupt ~line 2822) + +## Task: Fix /cancel not actually preventing task result display +User reported `/cancel` showed "canceled" but the agent's response still rendered. Root cause: `drainAgentQueue()` awaits `orchestrator.assign()` — when `killAgent()` terminates the process, the promise resolves with whatever output was produced before death. `drainAgentQueue` then continued to `displayTaskResult()` without checking if the task had been canceled. Fix: after `assign()` returns, check `this.agentActive.has(agent)` — if `cancelTeammateInThread` already removed it, skip result display (just clean up activity lines and `continue`). + +### Files changed +- `packages/cli/src/cli.ts` (~line 3061, added agentActive guard after assign) + +## Task: Replace killAgent with AbortSignal-based cancellation +User requested a cancellation object pattern instead of adapter-level `killAgent()`. Implemented using Node's built-in `AbortController`/`AbortSignal`. + +### Architecture +1. **Caller owns the lifecycle:** `cli.ts` creates an `AbortController` per running task in `drainAgentQueue()`, stores it in `abortControllers` Map keyed by teammate name. +2. **Signal flows through:** `TaskAssignment.signal` → `orchestrator.assign()` → `adapter.executeTask()` options → `spawnAndProxy()` / Copilot session. +3. **Adapters react to abort:** + - `CliProxyAdapter.spawnAndProxy()` — `signal.addEventListener("abort", ...)` → `child.kill("SIGTERM")` with 5s → SIGKILL escalation. Listener and kill timer cleaned up in the process close handler. + - `CopilotAdapter.executeTask()` — `signal.addEventListener("abort", ...)` → `session.disconnect()`. Listener cleaned up in finally block. + - `EchoAdapter` — accepts signal in signature (no-op). +4. **Cancel paths simplified:** `cancelTeammateInThread()` and `cmdInterrupt()` now call `controller.abort()` (synchronous) instead of `await adapter.killAgent(teammate)`. +5. **Reset path:** `cmdClear()` aborts all controllers before clearing state. + +### What was removed +- `killAgent()` method from `AgentAdapter` interface (adapter.ts) +- `killAgent()` implementation from `CliProxyAdapter` (cli-proxy.ts) +- `activeProcesses` Map from `CliProxyAdapter` (no longer needed — signal listener handles kill internally) +- `getAdapter()` dependency from `ActivityManagerDeps` interface +- `cancelRunningTask()` from `ActivityManager` (dead code — `[cancel]` verb now populates input box instead) +- `cancelTask()` from cli.ts (dead code — never called after `[cancel]` verb change) +- `getAdapter()` call from activity manager deps wiring in cli.ts +- Unused `tp` import from activity-manager.ts (caught by Biome) + +### Key decisions +- Used Node's built-in `AbortController`/`AbortSignal` instead of a custom EventEmitter — standard API, no dependencies, well-understood lifecycle. +- Cancel is now synchronous from the caller's perspective — `controller.abort()` fires immediately, the adapter reacts asynchronously, and the existing `agentActive` guard in `drainAgentQueue` handles skipping the result. +- Kept `getAdapter()` on Orchestrator (still useful for other purposes) — just removed the cancel-path usage. +- `activeProcesses` Map fully removed from `CliProxyAdapter` — the only purpose was external kill access, which the signal now handles internally. + +### Build & test +- TypeScript compiles cleanly (strict mode) +- 279 CLI tests pass +- Biome clean (removed one unused import) + +### Files changed +- `packages/cli/src/types.ts` (added `signal?: AbortSignal` to TaskAssignment) +- `packages/cli/src/adapter.ts` (added `signal` to executeTask options, removed `killAgent` from interface) +- `packages/cli/src/orchestrator.ts` (passes signal through to adapter) +- `packages/cli/src/adapters/cli-proxy.ts` (signal listener in spawnAndProxy, removed killAgent + activeProcesses) +- `packages/cli/src/adapters/copilot.ts` (signal listener for session.disconnect) +- `packages/cli/src/adapters/echo.ts` (accepts signal in signature) +- `packages/cli/src/activity-manager.ts` (removed getAdapter dep, cancelRunningTask, unused import) +- `packages/cli/src/cli.ts` (abortControllers map, abort in cancel/interrupt, removed dead cancelTask) + +## Task: Extract slash commands from cli.ts — Phase 4 +Extracted all slash commands into a new `commands.ts` module (1612 lines). cli.ts went from 3990 → 2606 lines (-35%). + +### New module +**`commands.ts`** — `CommandManager` class with typed `CommandsDeps` interface. Contains: +- `registerCommands()` — all 16 slash command definitions +- `dispatch()` — command routing +- All `cmd*` methods: Status, Debug, Cancel, Interrupt, Init, Clear, Compact, Retro, Copy, Help, User, Btw, Script, Theme +- Supporting helpers: `queueDebugAnalysis`, `cancelTeammateInThread`, `getThreadTeammates`, `getThreadTaskCounts`, `buildSessionMarkdown`, `doCopy`, `feedCommand`, `printBanner` +- Public methods used by cli.ts: `doCopy()`, `feedCommand()`, `printBanner()`, `dispatch()`, `registerCommands()`, `buildSessionMarkdown()`, `cancelTeammateInThread()`, `getThreadTeammates()` + +### Key decisions +- CommandManager receives deps via typed `CommandsDeps` interface with closure-backed getters for shared mutable state (same pattern as other extracted modules) +- `const repl = this` in `start()` captures the REPL instance for getter-based dep closures +- `cmdClear` calls `this.printBanner()` on its own class (not a dep) since the method was co-extracted +- Added `clearPastedTexts()` to deps since `pastedTexts` is only used by `cmdClear` among commands, but the Map itself stays in cli.ts +- `serviceView` getter stays in cli.ts since it's referenced by the deps object +- `refreshTeammates()` stays in cli.ts since `displayTaskResult()` (non-command) calls it +- Removed unused imports from cli.ts: `existsSync`, `readdirSync`, `readFileSync`, `stat`, `basename`, `resolve`, `colorToHex`, `relativeTime`, `cmdConfigure`, `importTeammates`, `copyTemplateFiles`, `buildImportAdaptationPrompt` + +### Cumulative extraction totals +- Phase 1: 6815 → 5549 (5 modules) +- Phase 2: 5549 → 4159 (2 modules) +- Phase 3: 4494 → 3970 (2 modules) +- Phase 4: 3990 → 2606 (1 module, this task) +- Total reduction: 6815 → 2606 (-62%) + +### Build & test +- TypeScript compiles cleanly (strict mode) +- Biome lint clean (auto-fixed 2 unused imports: `ActivityEvent` in commands.ts, one in cli.ts) +- 279 CLI tests pass, 561 consolonia tests pass (840 total) + +### Files changed +- `packages/cli/src/commands.ts` (NEW ~1612 lines) +- `packages/cli/src/cli.ts` (refactored — 3990 → 2606 lines) +- `packages/cli/src/index.ts` (new exports: CommandManager, CommandsDeps) + +## Task: Extract modules from cli.ts — Phase 5 (under 2000 lines) +Three extractions to bring cli.ts from 2606 → 1986 lines (-24%, **under the 2000-line target**). + +### New modules +1. **`conversation.ts`** (~119 lines) — `ConversationManager` class with typed `ConversationManagerDeps` interface. Contains: conversation history + summary state, `storeInHistory()`, `buildContext()`, `maybeQueueSummarization()`, `preDispatchCompress()`, and the context budget constants (`TARGET_CONTEXT_TOKENS`, `CONV_HISTORY_CHARS`). + +2. **`feed-renderer.ts`** (~400 lines) — `FeedRenderer` class with typed `FeedRendererDeps` interface. Contains: `feedLine()`, `feedMarkdown()`, `feedUserLine()`, `printUserMessage()`, `makeSpan()`, `wordWrap()`, `displayTaskResult()`, `displayFlatResult()`. Owns the `_userBg` color constant and the markdown theme config. + +### Delegation boilerplate elimination +Removed ~225 lines of pure pass-through delegation methods across 7 managers: +- **ThreadManager**: 15 methods (threads, focusedThreadId, containers, createThread, getThread, appendThreadEntry, renderThreadHeader, renderThreadReply, renderTaskPlaceholder, toggleThreadCollapse, toggleReplyCollapse, updateFooterHint, buildThreadClipboardText, threadFeedMarkdown, updateThreadHeader) +- **HandoffManager**: 9 methods (renderHandoffs, showHandoffDropdown, handleHandoffAction, auditCrossFolderWrites, showViolationWarning, handleViolationAction, handleBulkHandoff, pendingHandoffs, autoApproveHandoffs) +- **RetroManager**: 5 methods +- **OnboardFlow**: 9 methods (replaced with direct calls + inline closures for printAgentOutput) +- **Wordwheel**: 7 methods +- **ActivityManager**: 4 methods +- **StartupManager**: 4 methods + +All call sites updated to use `this.manager.method()` directly. CommandsDeps updated to receive `conversation: ConversationManager` instead of separate `conversationHistory`/`conversationSummary` fields. + +### Key decisions +- `feedLine`, `feedMarkdown`, `feedUserLine`, `makeSpan` kept as thin 1-line delegations in cli.ts (called from 30+ sites across cli.ts and passed as closures to 8 module deps — renaming every site would be churn) +- `shiftAllContainers` getter kept in cli.ts because it adds activity-index shifting logic on top of the base ThreadManager method +- FeedRenderer deps use getters for lazy resolution (chatView/threadManager don't exist when the renderer is constructed) +- `registerUserAvatar` delegation removed — inlined the 2-line body (onboardFlow call + userAlias assignment) at the single call site + +### Cumulative extraction totals +- Phase 1: 6815 → 5549 (5 modules) +- Phase 2: 5549 → 4159 (2 modules) +- Phase 3: 4494 → 3970 (2 modules) +- Phase 4: 3990 → 2606 (1 module) +- Phase 5: 2606 → 1986 (2 modules + boilerplate cleanup, this task) +- **Total reduction: 6815 → 1986 (-71%)** + +### Build & test +- TypeScript compiles cleanly (strict mode) +- Biome lint clean (auto-fixed unused imports) +- 279 CLI tests pass, 561 consolonia tests pass (840 total) + +### Files changed +- `packages/cli/src/conversation.ts` (NEW ~119 lines) +- `packages/cli/src/feed-renderer.ts` (NEW ~400 lines) +- `packages/cli/src/cli.ts` (refactored — 2606 → 1986 lines) +- `packages/cli/src/commands.ts` (updated deps: conversation instead of conversationHistory/Summary) +- `packages/cli/src/index.ts` (new exports: ConversationManager, FeedRenderer + types) + +## Task: Add GOALS.md to the teammate prompt stack +Handoff from Scribe: GOALS.md was added as a new standard teammate file (tracks active objectives and priorities). Injected it into the CLI prompt stack as item 2, between SOUL.md (IDENTITY) and WISDOM.md. + +### What was done +1. **types.ts** — Added `goals: string` field to `TeammateConfig` interface +2. **registry.ts** — Reads `GOALS.md` via `readFileSafe()` alongside SOUL.md and WISDOM.md +3. **adapter.ts** — Injects `` section between `` and `` in `buildTeammatePrompt()`. Added section reinforcement line for goals. Conditional on non-empty content (same pattern as WISDOM). +4. **commands.ts** — Updated `/retro` prompt to include GOALS.md in the review list +5. **Test files** — Added `goals: ""` to all 7 `TeammateConfig` object literals across 4 test files +6. **Inline configs** — Added `goals: ""` to 4 inline TeammateConfig constructions (cli-proxy.ts, cli.ts, onboard-flow.ts ×2) + +### Prompt stack order +`` (SOUL.md) → `` (GOALS.md) → `` (WISDOM.md) → `` → ... + +### Build & test +- TypeScript compiles cleanly (strict mode) +- 279 CLI tests pass +- Biome lint clean + +### Files changed +- types.ts, registry.ts, adapter.ts, commands.ts, cli.ts, cli-proxy.ts, onboard-flow.ts, adapter.test.ts, echo.test.ts, orchestrator.test.ts, registry.test.ts + +## Task: Fix missing [reply] [copy thread] verbs at bottom of threads +Root cause: when `displayThreadedResult()` was extracted from `cli.ts` into `thread-manager.ts` during the Phase 2 module extraction, the `insertThreadActions()` call that renders [reply] [copy thread] verbs was dropped. The `displayCanceledInThread()` method in the same file still had the correct code. Fix: added the missing `insertThreadActions()` block plus the show/hide logic and `updateThreadHeader()` call, matching the pattern in `displayCanceledInThread()`. + +### Files changed +- `packages/cli/src/thread-manager.ts` + +## Task: Fix "Cannot read properties of undefined (reading 'trim')" crash +Root cause: `registerUserAvatar()` in `onboard-flow.ts` constructed a `TeammateConfig` without the `goals` field (added in today's GOALS.md integration). When the user's avatar config was later passed to `buildTeammatePrompt()`, `teammate.goals.trim()` threw because `goals` was `undefined`. Fix: added `goals` field (read from `GOALS.md` via `readFileSync` with catch fallback to `""`) to the registered config, matching the pattern already used for `soul` and `wisdom`. + +### Files changed +- `packages/cli/src/onboard-flow.ts` + +## Task: Rework bundled personas into folder-based SOUL/WISDOM templates +Changed the bundled CLI persona templates from single markdown files under `packages/cli/personas/*.md` to per-persona folders under `packages/cli/personas//` containing `SOUL.md` and `WISDOM.md`. Updated `loadPersonas()` to discover directories, parse metadata from each `SOUL.md` frontmatter, and load the paired `WISDOM.md`. Updated `scaffoldFromPersona()` to copy both template files with `` replacement instead of synthesizing wisdom inline. Also updated persona tests to validate the new `Persona` shape (`soul` + `wisdom`) and the new scaffold behavior. + +### Key decisions +- Kept persona metadata in `SOUL.md` frontmatter so onboarding still reads `persona`, `alias`, `tier`, and `description` from one source of truth. +- Made `WISDOM.md` a first-class template file per persona so persona-specific wisdom can evolve independently instead of being hardcoded in the scaffolder. +- Created the new folder assets for all bundled personas, and updated the prompt-engineer template's internal references from `packages/cli/personas/*.md` to `packages/cli/personas/**`. +- `npm run build` for `packages/cli` passed. `vitest` still could not start in this sandbox due Vite `spawn EPERM`. +- Sandbox policy blocked deletion of the old top-level `packages/cli/personas/*.md` files in this session, so the new folder layout is in place and the loader ignores those files, but the stale legacy files still need to be removed outside this sandboxed run. + +### Files changed +- `packages/cli/src/personas.ts` +- `packages/cli/src/personas.test.ts` +- `packages/cli/personas/*/SOUL.md` +- `packages/cli/personas/*/WISDOM.md` + +### Next +- Remove the legacy top-level `packages/cli/personas/*.md` files in a non-blocked session so the repo contains only the folder-based templates. + +## Task: Make persona aliases the canonical bundled persona names +Updated persona loading and onboarding so the alias is now the primary bundled persona identity. `loadPersonas()` only accepts persona folders whose directory name matches the `alias:` in `SOUL.md`, sorts by tier then alias, and now tolerates Windows CRLF frontmatter when parsing persona files. Onboarding now shows `@alias` as the primary label and prompts for alias overrides using the alias as the default installed teammate name. Seeded real role-specific starter wisdom into the alias folders (`packages/cli/personas//WISDOM.md`) instead of the previous placeholder text. + +### Key decisions +- Treat the alias folder name as canonical and ignore legacy role-slug folders if both exist. +- Keep `persona` as the secondary human-readable role label; use alias as the selectable/installable persona name. +- Fix the loader's frontmatter regex to accept `\r\n` as well as `\n`; otherwise Windows persona files silently fail to load. +- Copy persona assets into alias-named folders for now because this sandbox still blocks clean removal/move of the legacy role-slug folders and top-level `packages/cli/personas/*.md` files. + +### Files changed +- `packages/cli/src/personas.ts` +- `packages/cli/src/personas.test.ts` +- `packages/cli/src/onboard-flow.ts` +- `packages/cli/personas/beacon/WISDOM.md` +- `packages/cli/personas/blueprint/WISDOM.md` +- `packages/cli/personas/engine/WISDOM.md` +- `packages/cli/personas/forge/WISDOM.md` +- `packages/cli/personas/pipeline/WISDOM.md` +- `packages/cli/personas/pixel/WISDOM.md` +- `packages/cli/personas/prism/WISDOM.md` +- `packages/cli/personas/neuron/WISDOM.md` +- `packages/cli/personas/orbit/WISDOM.md` +- `packages/cli/personas/tempo/WISDOM.md` +- `packages/cli/personas/scribe/WISDOM.md` +- `packages/cli/personas/lexicon/WISDOM.md` +- `packages/cli/personas/sentinel/WISDOM.md` +- `packages/cli/personas/shield/WISDOM.md` +- `packages/cli/personas/watchtower/WISDOM.md` +- `packages/cli/personas/quill/WISDOM.md` + +### Verification +- `npm run build` in `packages/cli` +- Node import of `packages/cli/dist/personas.js` confirmed `loadPersonas()` returns the alias-backed persona set + +## Task: Verify and attempt cleanup of stale bundled persona folders +User pointed out that the old bundled persona folders were still present after the alias-based conversion. I inspected `packages/cli/personas`, identified the remaining stale entries by comparing each folder name to the `alias:` in its `SOUL.md`, and confirmed the current leftover set is `qa`, `security`, `sre`, `swe`, `tech-writer`, plus the legacy top-level `*.md` persona files. I attempted cleanup through PowerShell deletion, `apply_patch`, and direct .NET file APIs, but this sandbox denied file deletions at the filesystem level. `npm run build` in `packages/cli` still passed. + +### Key decisions +- Treat a persona directory as stale when `alias:` in `SOUL.md` does not match the directory name. +- Report the exact remaining stale entries instead of claiming cleanup succeeded. +- Do not log deletion as completed because the sandbox blocked it. + +### Files changed +- `.teammates/beacon/memory/2026-03-29.md` + +### Next +- Remove the remaining stale persona folders/files in a session that permits deletions, then re-list `packages/cli/personas` to confirm only alias-backed folders remain. + +## Task: Fix Codex activity collapsing to one "Exploring" line +Analyzed recent Codex JSONL logs under `.teammates\.tmp\debug\`. Root cause: the live parser handled `command_execution` and older tool-call events, but ignored Codex `item.type: file_change` entries. That meant the UI only saw reads/greps and collapsed the whole run into one `Exploring` line. Added `file_change` parsing in `parseCodexJsonlLine()` so `item.started` emits grouped `Edit`/`Write` activity and failed `item.completed` emits error activity. Added focused parser tests for started/completed `file_change` events. Verified with a real recent log that the collapsed output now includes `Exploring`, `Edit`, `Write`, and error phases instead of only research. `npm run build` in `packages/cli` passed; `vitest` still could not start in this sandbox due Vite `spawn EPERM`. + +### Key decisions +- Treat `file_change` as the missing Codex activity shape, not a rendering bug in the activity UI. +- Emit grouped activity per change kind: `add` -> `Write`, `update`/`delete` -> `Edit`. +- Emit normal activity on `item.started` for live feedback, and only emit `item.completed` for failed file changes so successful completions do not double-render. + +### Files changed +- `packages/cli/src/activity-watcher.ts` +- `packages/cli/src/activity-watcher.test.ts` +- `.teammates/beacon/memory/decision_codex_activity_from_debug_jsonl.md` + +### Next +- Restart the running CLI so it loads the rebuilt parser, then rerun a Codex task and confirm `[show activity]` shows edit/write phases live. + +## Task: Update CLI adapter passthrough defaults for Claude and Codex +Ran `claude --help`, `codex --help`, and `codex exec --help` to capture the current option sets before changing adapter argv construction. Kept the existing passthrough flow (`agentPassthrough` -> `extraFlags`) and changed the built-in Codex defaults from `--full-auto` plus `workspace-write` to explicit `-a never -s danger-full-access`, while preserving `--ephemeral --json`. Added focused preset tests and updated CLI usage text to make passthrough visible. + +### Key decisions +- Treat passthrough as an adapter argv concern, not an orchestrator change. +- Use explicit Codex flags instead of `--full-auto` so approval and sandbox behavior are deterministic and match the requested defaults. +- Keep teammate-level sandbox config as a higher-priority default source; if no teammate sandbox is set, Codex now falls back to `danger-full-access`. +- Leave Copilot for a follow-up task, per the user's request. + +### Build & verification +- `npm run build` in `packages/cli` passed cleanly. +- `npx biome check --write ...` on the touched CLI files reported no fixes needed. +- `npx vitest run src/cli-args.test.ts src/adapters/echo.test.ts src/adapters/presets.test.ts` failed to start in this sandbox because Vite hit `spawn EPERM`. + +### Files changed +- `packages/cli/src/adapters/presets.ts` +- `packages/cli/src/adapters/presets.test.ts` +- `packages/cli/src/adapters/codex.ts` +- `packages/cli/src/adapters/cli-proxy.ts` +- `packages/cli/src/cli-args.ts` + +### Next +- Restart the CLI before manual testing so the rebuilt adapter code is loaded. +- Extend the same pass-through treatment to the Copilot adapter next. + +## Task: Fix Codex activity under-reporting in the renderer +User clarified the debug JSONL already contained both reads and edits, so the bug was in activity rendering. Fixed two renderer-side gaps: (1) the per-task Claude/Codex file watchers were skipping whatever bytes were already in the file when the watcher attached, which could drop the earliest Codex commands; (2) the collapse logic converted even a single `Read`/`Grep` event into a generic `Exploring` line, hiding concrete evidence that parsing was working. + +### Key decisions +- Treat this as a display/watcher timing bug, not an execution bug in Codex. +- Start per-task debug-log watchers from byte `0` instead of the current file size, because each task log is unique and should be parsed in full from the moment activity watching begins. +- Preserve single research events (`Read`, `Grep`, etc.) as first-class lines; only collapse consecutive research runs of 2+ events into `Exploring`. + +### Build & verification +- `npm run build` in `packages/cli` passed cleanly. +- `npx biome check --write src\\activity-watcher.ts src\\activity-watcher.test.ts` reported no fixes needed. +- Verified the compiled watcher against the current Codex debug log in `.teammates\\.tmp\\debug\\beacon-2026-03-29T21-34-44-206Z.md`; the first parsed events now include the early `Read` lines instead of starting after the watcher attach point. + +### Files changed +- `packages/cli/src/activity-watcher.ts` +- `packages/cli/src/activity-watcher.test.ts` +- `.teammates/beacon/memory/decision_codex_activity_from_debug_jsonl.md` + +### Next +- Restart the running CLI so Node loads the rebuilt watcher code. +- Re-run a Codex task and toggle `[show activity]`; early reads should now appear, and a lone read/grep will render directly instead of as `Exploring (1× ...)`. + +## Task: Fix Codex "unexpected argument '-a'" error +Root cause: `CODEX_PRESET` in `presets.ts` was passing `-a never` to `codex exec`, but `codex exec` has no `-a` flag. The flag was added earlier today when switching from `--full-auto` to explicit flags, but `-a` (approval mode) doesn't exist in Codex's CLI — only `-s` (sandbox) does. Fix: removed `args.push("-a", "never")` from the Codex preset. Updated preset test expectations to match. + +### Files changed +- `packages/cli/src/adapters/presets.ts` +- `packages/cli/src/adapters/presets.test.ts` + +### Next +- Restart the CLI so the rebuilt preset is loaded, then re-test a Codex task. + +## Task: Switch Copilot adapter from SDK to CLI +Replaced the `@github/copilot-sdk`-based `CopilotAdapter` with a thin `CliProxyAdapter` wrapper that spawns `copilot -p - --allow-all -s`. This unifies all three coding agents (Claude, Codex, Copilot) under the same subprocess-based adapter pattern. + +### What was done +1. **`presets.ts`** — Added `COPILOT_PRESET`: spawns `copilot -p - --allow-all -s` with `stdinPrompt: true`. `-p -` reads prompt from stdin, `--allow-all` enables full permissions, `-s` gives clean text output. Model override via `--model`. Added to `PRESETS` registry. +2. **`copilot.ts`** — Rewrote from 391-line SDK adapter to 28-line `CliProxyAdapter` wrapper (same pattern as `claude.ts` and `codex.ts`). `CopilotAdapterOptions` now matches the CLI pattern: `model`, `extraFlags`, `commandPath`. +3. **`cli-args.ts`** — Changed from dynamic SDK import to direct `CopilotAdapter` import. Now passes `agentPassthrough` as `extraFlags` (was missing before). Updated usage text to list copilot. Removed `"copilot"` from hardcoded available list (now discovered via `PRESETS`). +4. **`package.json`** — Removed `@github/copilot-sdk` dependency. Removed `postinstall` script (was patching SDK ESM imports). +5. **`presets.test.ts`** — Added 2 tests for `COPILOT_PRESET` (default args + model override). Added 1 test for Codex model override. + +### What was removed +- `@github/copilot-sdk` dependency (and transitive `vscode-jsonrpc`) +- `scripts/patch-copilot-sdk.cjs` (SDK ESM patch — no longer needed) +- SDK-specific code: `CopilotClient`, `CopilotSession`, `SessionEvent`, `approveAll`, event monkey-patching, `ensureClient()`, session management, JSON-RPC communication +- ~360 lines of SDK-specific adapter code + +### Key decisions +- Used `-p -` (dash convention) for stdin prompt reading — avoids command-line length limits with large prompts +- Used `--allow-all` instead of individual `--allow-all-tools --allow-all-paths --allow-all-urls` — equivalent but shorter +- Used `-s` (silent) for clean text output — no stats or progress noise to parse +- Did NOT use `--output-format json` yet — start simple with text, can add JSON later for activity tracking +- All CLI passthrough flags now work for copilot (e.g. `teammates copilot --reasoning-effort high --add-dir /tmp`) + +### Build & test +- TypeScript compiles cleanly (strict mode) +- Biome lint clean +- 6 preset tests pass (including 2 new copilot tests) + +### Files changed +- `packages/cli/src/adapters/presets.ts` +- `packages/cli/src/adapters/copilot.ts` (rewritten) +- `packages/cli/src/cli-args.ts` +- `packages/cli/package.json` +- `packages/cli/src/adapters/presets.test.ts` + +### Next +- Restart the CLI so it loads the new adapter code +- Test with `teammates copilot` to verify `copilot -p -` reads from stdin correctly +- `scripts/patch-copilot-sdk.cjs` can be deleted in a non-sandboxed session +- Consider adding `--output-format json` + `parseOutput` later for structured activity tracking + +## Task: Fix Copilot empty debug log + missing [show activity] +Root cause: the new Copilot CLI preset was wrong in two ways. (1) `copilot -p -` does not read stdin, so Copilot treated the prompt as empty and returned the same "empty message" text the user saw in the debug log/UI. (2) The preset ran in plain text mode, so there was no structured tool stream for `[show activity]` to parse. + +### What was changed +1. **`presets.ts`** — Switched Copilot to JSONL output (`--output-format json --stream off --no-color`) and added `parseOutput()` to extract the last non-empty `assistant.message`. On Windows, the preset now launches through `powershell.exe -EncodedCommand` so it can read the full prompt from the temp prompt file and pass it to `copilot -p` without stdin or shell-quoting issues. Added `-NonInteractive` plus `$ProgressPreference = 'SilentlyContinue'` to keep stderr clean. +2. **`activity-watcher.ts`** — Added `parseCopilotJsonlLine()` and `watchCopilotDebugLog()` for Copilot JSONL events. Maps `tool.execution_start` events like `view`, `shell`, `grep`, `glob`, `edit`, and `write` into the existing activity labels. +3. **`cli-proxy.ts`** — Wired the Copilot adapter to tail its paired `.teammates\\.tmp\\debug\\*.md` log file during execution, the same way Codex tails its JSONL debug log. +4. **Tests** — Added focused Copilot parser/preset tests. Clean `npm run build` passed after the change. Direct runtime sanity check against the compiled preset confirmed the Windows wrapper now emits JSONL with `tool.execution_start`, includes `toolName:\"view\"`, and returns the final `assistant.message`. + +### Key decisions +- Do not keep trying to force stdin into Copilot prompt mode; use the prompt file as the source of truth and wrap the Copilot call on Windows. +- Use Copilot's native JSONL mode instead of inventing a text parser; the tool stream is already there. +- Tail the existing paired debug log file in-memory for activity rather than creating any new disk intermediary. + +### Files changed +- `packages/cli/src/adapters/presets.ts` +- `packages/cli/src/adapters/copilot.ts` +- `packages/cli/src/activity-watcher.ts` +- `packages/cli/src/adapters/cli-proxy.ts` +- `packages/cli/src/activity-watcher.test.ts` +- `packages/cli/src/adapters/presets.test.ts` +- `packages/cli/src/index.ts` + +### Next +- Restart the running CLI process so it loads the rebuilt Copilot preset/watcher code. +- Re-run a Copilot task and toggle `[show activity]`; you should now see real `Read` / `Glob` / `Grep` / `Edit` lines instead of an empty block. + +## Task: Fix Copilot "filename or extension is too long" error on Windows +Root cause: the PowerShell `-EncodedCommand` wrapper read the prompt file into `$prompt`, then passed it as `-p $prompt` to copilot. When PowerShell called the `copilot` npm shim, which in turn called `node.exe copilot.exe -p `, the command line exceeded Windows' ~32K character limit for `CreateProcessW`. + +### Investigation +- Tested `copilot.exe -p -` with stdin piping → `-` treated as literal prompt text (not stdin). Dead end. +- Tested `copilot.exe -p @filepath` → `@filepath` treated as literal prompt text. Dead end. +- Tested copilot in **interactive mode** (no `-p` flag) with stdin piping → **works**. Copilot reads one message from stdin, processes it, and exits on EOF. +- The copilot CLI has NO `--prompt-file` or stdin flag. Interactive mode is the only path. + +### Fix +Replaced the entire PowerShell `-EncodedCommand` wrapper with simple stdin piping: +- `command: "copilot"` (same on all platforms — no more Windows-specific PowerShell wrapper) +- `stdinPrompt: true` — `spawnAndProxy()` pipes the prompt to copilot's stdin +- No `-p` flag — copilot reads stdin in interactive mode, processes the message, exits on EOF +- All other flags preserved: `--allow-all --output-format json --stream off --no-color` + +### Key decisions +- Stdin piping is the only way to get large prompts to copilot without hitting the command-line limit. +- The `-p` flag is fundamentally incompatible with large prompts on Windows since the text becomes a command-line argument. +- Interactive mode + stdin + EOF behaves identically to `-p` mode for single-message tasks. +- This simplifies the preset from 30 lines (with platform branching) to 15 lines (uniform across platforms). + +### Build & verification +- `npm run build` in `packages/cli` passed cleanly +- `npx biome check --write` on changed files — no fixes needed +- Verified via direct `copilot.exe` invocation that stdin piping with `--output-format json --stream off` returns proper JSONL with `assistant.message` events + +### Files changed +- `packages/cli/src/adapters/presets.ts` +- `packages/cli/src/adapters/presets.test.ts` + +### Next +- Restart the CLI so the rebuilt preset is loaded, then re-test a Copilot task. + +## Task: Diagnose Copilot "filename or extension is too long" error +User reported CLIXML PowerShell error from Copilot adapter: `Program 'node.exe' failed to run: The filename or extension is too long`. Error originated at `copilot.ps1:24`. + +### Root cause +User's running CLI process was still loaded with the old Copilot preset code that used `powershell.exe -EncodedCommand` to pass the full teammate prompt as a Base64-encoded command-line argument. For large prompts (~50-100KB), this exceeds Windows' 32,767-character command-line limit. The current source and built `dist/` already use stdin piping (`stdinPrompt: true`) which has no length limit — confirmed working with 116KB test prompts. The user just needed to restart the CLI to pick up the rebuilt code. + +### Key decisions +- No code changes needed — the fix (stdin piping) was already present in source and dist. +- Root cause was stale cached modules from a pre-rebuild CLI session. +- Confirmed via direct spawn test that copilot handles 116KB stdin prompts without error. + +### Files changed +- None (diagnosis only) diff --git a/.teammates/beacon/memory/2026-03-30.md b/.teammates/beacon/memory/2026-03-30.md new file mode 100644 index 0000000..9656183 --- /dev/null +++ b/.teammates/beacon/memory/2026-03-30.md @@ -0,0 +1,348 @@ +--- +type: daily +--- +# 2026-03-30 + +## Task: Run standup (morning) +Prepared and delivered Beacon's standup update. + +### Key decisions +- No code changes were needed. +- No teammate handoffs were needed; this turn was a status report only. + +### Files changed +- `.teammates/beacon/memory/2026-03-30.md` + +## Task: Explain why terminal verbs may not be clickable on one Windows machine +Inspected the Consolonia click path and CLI app setup to answer why `[verbs]` can work for one Windows user and fail for another. Verified that the app enables mouse mode, but `ChatView` only reacts to parsed SGR mouse sequences (`ESC [ < ... M/m`). There is no fallback for other mouse-reporting formats or for terminals that swallow mouse input. + +### Key decisions +- Gave a code-based diagnosis rather than assuming it was a generic Windows issue. +- Identified terminal capability/configuration as the likely differentiator between the two machines. +- Called out the most likely causes: different terminal host, unsupported mouse protocol, or an intermediary layer consuming mouse events. + +### Files changed +- `.teammates/beacon/memory/2026-03-30.md` +- `.teammates/beacon/memory/reference_terminal_mouse_sgr.md` + +## Task: Run standup (evening) +Delivered evening standup summarizing today's work. + +### Key decisions +- No code changes; status report only. + +### Files changed +- `.teammates/beacon/memory/2026-03-30.md` + +## Task: Expand Copilot activity parsing +Analyzed Copilot debug log (`beacon-2026-03-30T00-58-38-911Z.md`) and expanded `mapCopilotToolCall` to handle all Copilot CLI tool names. The existing parser only handled 8 tool names (`view`, `edit`, `write`, `create`, `grep`, `search`, `glob`, `shell`/`bash`), but Copilot uses many more: `powershell`, `task`, `read_agent`, `web_search`, `web_fetch`, `github-mcp-server-*`, plus plumbing tools that should be silently dropped. + +### Key decisions +- Added `COPILOT_PLUMBING` set to explicitly filter internal tools (`report_intent`, `store_memory`, `fetch_copilot_cli_documentation`, `list_powershell`, `list_agents`, `read_powershell`, `write_powershell`, `stop_powershell`, `sql`). +- Added `powershell` to the shell/bash case group — this is the #1 missing mapping since Copilot uses `powershell` not `bash` on Windows. +- Added `task` → Agent, `read_agent`/`write_agent` → Agent. +- Added `web_search` → WebSearch, `web_fetch` → WebFetch. +- Added prefix-based matching for `github-mcp-server-*` tools: `search_*` → Search, `get_*`/`list_*`/`issue_read`/`pull_request_read` → Read, fallback → Search. +- Enhanced `parseCopilotJsonlLine` to use event `timestamp` field for accurate elapsed time instead of wall-clock `receivedAt`. +- Could not run build/test (pwsh not available in sandbox). Code reviewed for correctness. Needs build verification after CLI restart. +- **Build verified** in the "Rebuild everything" task — test expected `isError: false` was missing from `file_change` test assertion (fixed). + +### Files changed +- `packages/cli/src/activity-watcher.ts` — expanded `mapCopilotToolCall`, added `COPILOT_PLUMBING` set, timestamp-based elapsed time in `parseCopilotJsonlLine` +- `packages/cli/src/activity-watcher.test.ts` — added 12 new tests covering: powershell→Bash, powershell grep→Grep, task→Agent, read_agent→Agent, web_search→WebSearch, web_fetch→WebFetch, github-mcp-server-search_code→Search, github-mcp-server-get_file_contents→Read, plumbing drops (report_intent, store_memory), event timestamp usage +- `.teammates/beacon/memory/2026-03-30.md` + +## Task: Rebuild everything +Clean rebuild of all three packages (consolonia → recall → cli) with lint and test verification. + +### Key decisions +- Cleaned all `dist/` folders before rebuilding per wisdom ("Clean dist before rebuilding"). +- All three packages compiled cleanly with `tsc`. +- Biome lint auto-fixed 3 files: removed unused imports (`pen`, `ActivityEvent`, `ThreadEntry`) from `packages/cli/src/cli.ts`. +- Rebuilt cli after lint fixes to keep dist in sync. +- Fixed `activity-watcher.test.ts` — `file_change` test was missing `isError: false` in expected output (from earlier unverified Copilot parsing work). +- Fixed `compact.test.ts` — "does not compact current week's dailies" used `toISOString().slice(0,10)` (UTC date) but `compactDailies` uses local date for week calculation. On timezone boundaries these differ, causing the test to create a file in a different week than "current." Fixed to use local date consistently. +- All 959 tests pass across all three packages (561 consolonia + 94 recall + 304 cli). + +### Files changed +- `packages/cli/src/cli.ts` — removed unused imports (auto-fixed by Biome) +- `packages/cli/src/activity-watcher.test.ts` — added `isError: false` to expected output +- `packages/cli/src/compact.test.ts` — fixed timezone-sensitive date in "does not compact current week" test +- `.teammates/beacon/memory/2026-03-30.md` + +## Task: Add classic mouse protocol support for terminal verbs +Patched Consolonia's mouse input layer so clickable verbs work with both SGR mouse reporting (`ESC [ < ...`) and classic xterm/ANSI mouse packets (`ESC [ M ...`). Also broadened the mouse-enable escape sequence to request classic tracking alongside SGR, then verified the change with focused build and test runs. + +### Key decisions +- Treat this as an input-compatibility bug in Consolonia, not a Windows-specific CLI issue. +- Keep existing SGR behavior intact while adding classic CSI `M` decoding for terminals that fall back to older mouse packets. +- Enable classic tracking (`?1000h`) alongside existing motion + SGR modes so supported terminals can emit whichever mouse packet family they actually use. +- Verified with `npm run build` and focused Vitest coverage in `packages/consolonia`; no broader CLI changes were needed in this turn. + +### Files changed +- `packages/consolonia/src/input/mouse-matcher.ts` — added classic CSI `M` mouse decoding while preserving SGR semantics +- `packages/consolonia/src/ansi/esc.ts` — enabled and disabled classic mouse tracking alongside existing SGR mode +- `packages/consolonia/src/__tests__/input.test.ts` — added classic mouse parser and InputProcessor coverage +- `packages/consolonia/src/__tests__/ansi.test.ts` — updated mouse mode expectations +- `.teammates/beacon/memory/2026-03-30.md` + +### Next +- Restart the running `teammates` CLI before retesting, because the existing Node process will still have the old Consolonia code loaded. + +## Task: Bump package versions to 0.7.1 +Updated the workspace release references from `0.7.0` to `0.7.1` across the three owned packages, the CLI settings file, and the workspace lockfile package entries. + +### Key decisions +- Treated this as a release metadata change only; no source or migration behavior changed. +- Updated `.teammates/settings.json` alongside package manifests so the CLI's persisted version stays aligned with the workspace packages. +- Left `0.7.0` references in `packages/cli/MIGRATIONS.md` and `packages/cli/src/migrations.ts` unchanged because they describe the historical `0.7.0` migration path, not the current package version. + +### Files changed +- `packages/cli/package.json` +- `packages/recall/package.json` +- `packages/consolonia/package.json` +- `.teammates/settings.json` +- `package-lock.json` + +## Task: Audit all terminal mouse escape codes for full protocol support +Reviewed current `mouse-matcher.ts` and `esc.ts` to catalog all terminal mouse protocols. Identified two missing protocols (URXVT `?1015h` and UTF-8 `?1005h`) plus one optional protocol (SGR-Pixels `?1016h`). Delivered a complete protocol comparison table with parsing strategies. + +### Key decisions +- Research-only task; no code changes made. +- URXVT (`?1015h`) identified as highest priority — distinct wire format (`ESC [ Cb;Cx;Cy M` without `<`), easy to add as a new parser state. +- UTF-8 (`?1005h`) identified as medium priority — reuses X10 prefix but with multi-byte encoded coordinates. +- SGR-Pixels (`?1016h`) identified as low priority — same wire format as SGR, only difference is pixel vs cell coordinates. +- Highlight tracking (`?1001h`) is obsolete and safe to skip. +- Button-event tracking (`?1002h`) is already covered by `?1003h` (any-event), which is already enabled. + +### Files changed +- `.teammates/beacon/memory/2026-03-30.md` + +## Task: Implement URXVT, UTF-8, and SGR-Pixels mouse protocol support +Added full support for all remaining terminal mouse protocols in Consolonia's input layer. + +### Key decisions +- **URXVT** (`?1015h`): Added new `ReadingUrxvt` parser state in `MouseMatcher`. URXVT sends `\x1b[Cb;Cx;CyM` (decimal params without `<` prefix). Uses X10-style button encoding (button bits 3 = release). No conflict with EscapeMatcher because `M` is not in `CSI_FINAL_KEYS`. +- **UTF-8** (`?1005h`): No parser changes needed. UTF-8 mode uses the same `\x1b[M` prefix as X10 but with multi-byte UTF-8 encoded coordinates. Node.js decodes UTF-8 stdin automatically, so `charCodeAt(0) - 32` works for both modes. +- **SGR-Pixels** (`?1016h`): No parser changes needed. Same wire format as SGR but with pixel coordinates. Pixel coords pass through as-is; the caller must convert to cells if needed. +- Updated `esc.ts` to request all six tracking modes: `?1000h`, `?1003h`, `?1005h`, `?1006h`, `?1015h`, `?1016h`. Terminals pick the highest mode they support. +- All 576 consolonia tests pass including 15 new tests for URXVT parsing. + +### Files changed +- `packages/consolonia/src/input/mouse-matcher.ts` — added `ReadingUrxvt` state, `finalizeUrxvt()`, and URXVT-specific param accumulation +- `packages/consolonia/src/ansi/esc.ts` — expanded `mouseTrackingOn/Off` to include `?1005h`, `?1015h`, `?1016h` +- `packages/consolonia/src/__tests__/input.test.ts` — added 11 URXVT tests (press, release, wheel, motion, modifiers, large coords, bad param counts) + 1 InputProcessor URXVT integration test +- `packages/consolonia/src/__tests__/ansi.test.ts` — updated mouse mode escape sequence expectations +- `.teammates/beacon/memory/2026-03-30.md` + +## Task: Audit Consolonia escape code initialization per environment +Examined the full Consolonia init/cleanup path in `app.ts` and `esc.ts` to catalog every escape sequence sent, the order they're sent, and whether any environment-specific logic exists. + +### Key decisions +- Research-only task; no code changes made. +- Documented the full init sequence: alternate screen → hide cursor → bracketed paste → mouse tracking (6 modes) → clear screen. +- Documented cleanup sequence (reverse order): reset → mouse off → paste off → show cursor → alt screen off. +- Confirmed **zero environment detection** — no checks for `process.platform`, `TERM`, `COLORTERM`, or terminal emulator type. +- Strategy is "blast all modes, let the terminal sort it out" which maximizes compatibility. +- Identified that legacy cmd.exe has no mouse support at all, and some terminals may silently ignore unsupported DECSET codes. + +### Files changed +- `.teammates/beacon/memory/2026-03-30.md` + +## Task: Implement environment-aware terminal initialization +Added terminal environment detection and wired it into Consolonia's init/restore lifecycle so escape sequences are tailored to what the terminal actually supports. + +### Key decisions +- Created `terminal-env.ts` with `detectTerminal()` that probes `process.env` and `process.platform` to identify the terminal and return a `TerminalCaps` struct with boolean flags for each capability. +- Detects: Windows Terminal, VS Code, ConEmu, mintty, conhost, tmux, GNU screen, iTerm2, Alacritty, xterm-compatible, dumb terminals, and non-TTY pipes. +- Added `mouseOn()`/`mouseOff()` to `esc.ts` — uses full 6-mode mouse when SGR is supported, falls back to minimal `?1000h + ?1003h` when it isn't (e.g. GNU screen). +- Added `initSequence()` and `restoreSequence()` to `esc.ts` — compose the full startup/shutdown escape strings using detected caps + app options. +- Updated `App` class to call `detectTerminal()` at construction and use the new `initSequence()`/`restoreSequence()` builders instead of hardcoded sequences. +- Exposed `caps` getter on `App` for diagnostics. +- Exported `TerminalCaps` and `detectTerminal` from the consolonia public API. +- All 598 consolonia tests pass including 22 new tests for environment detection and init/restore sequence building. + +### Files changed +- `packages/consolonia/src/ansi/terminal-env.ts` — **new** — terminal environment detection +- `packages/consolonia/src/ansi/esc.ts` — added `mouseOn()`, `mouseOff()`, `initSequence()`, `restoreSequence()` +- `packages/consolonia/src/app.ts` — uses `detectTerminal()` and `initSequence()`/`restoreSequence()` instead of hardcoded sequences +- `packages/consolonia/src/index.ts` — exported `TerminalCaps` and `detectTerminal` +- `packages/consolonia/src/__tests__/ansi.test.ts` — added 22 tests for terminal-env detection and environment-aware sequences +- `.teammates/beacon/memory/2026-03-30.md` + +## Task: Diagnose persistent mouse failure on Windows — compare with external Consolonia +Compared our Consolonia mouse init against the external C#/.NET Consolonia project (`C:\source\consolonia`) to find why mouse events work in the external project but not in ours on the same Windows machine. + +### Key decisions +- **Root cause identified:** External Consolonia uses Win32 `SetConsoleMode()` API to disable `ENABLE_QUICK_EDIT_MODE` and enable `ENABLE_MOUSE_INPUT` + `ENABLE_EXTENDED_FLAGS`. Our package only sends ANSI escape sequences and relies on Node.js raw mode, which does NOT touch these flags. +- Quick Edit Mode intercepts all mouse clicks for text selection before they reach stdin — this is likely the #1 cause of the user's issue. +- Node.js `setRawMode(true)` adds `ENABLE_VIRTUAL_TERMINAL_INPUT` but does NOT disable `ENABLE_QUICK_EDIT_MODE` or enable `ENABLE_MOUSE_INPUT`. +- Proposed fix: use `koffi` (native FFI library) to call `SetConsoleMode` on Windows during terminal init. +- Research-only task; no code changes made yet. Awaiting user approval to implement. + +### Files changed +- `.teammates/beacon/memory/2026-03-30.md` +- `.teammates/beacon/memory/decision_win32_console_mode.md` + +## Task: Implement Win32 SetConsoleMode fix and bump to 0.7.2 +Implemented the actual fix for Windows mouse events by adding Win32 `SetConsoleMode()` calls via koffi FFI. This is the code fix for the root cause identified in the previous research task. Also bumped all package versions to 0.7.2. + +### Key decisions +- Created `win32-console.ts` module with `enableWin32Mouse()` and `restoreWin32Console()` functions. +- Uses `koffi` (native FFI library) as an optional dependency to call `kernel32.dll` — `GetStdHandle`, `GetConsoleMode`, `SetConsoleMode`. +- Lazy-binds kernel32 on first call; caches the binding. Gracefully returns `false` if koffi is not available. +- `enableWin32Mouse()` disables `ENABLE_QUICK_EDIT_MODE` and enables `ENABLE_MOUSE_INPUT` + `ENABLE_WINDOW_INPUT` + `ENABLE_EXTENDED_FLAGS`. Saves original mode for restoration. +- Wired into `App._setup()` (after `enableRawMode()`, before ANSI sequences) and `App._teardown()` (after ANSI restore, before `disableRawMode()`). +- koffi added as `optionalDependencies` in consolonia's package.json so builds don't fail on platforms without native compilation. +- Used `createRequire(import.meta.url)` for dynamic require in ESM context. +- Bumped consolonia, cli, and recall to 0.7.2. Updated `.teammates/settings.json` cliVersion. +- All 1000 tests pass (602 consolonia + 94 recall + 304 cli). + +### Files changed +- `packages/consolonia/src/ansi/win32-console.ts` — **new** — Win32 console mode FFI wrapper +- `packages/consolonia/src/app.ts` — wired `enableWin32Mouse()`/`restoreWin32Console()` into setup/teardown +- `packages/consolonia/src/index.ts` — exported `enableWin32Mouse` and `restoreWin32Console` +- `packages/consolonia/src/__tests__/ansi.test.ts` — added win32-console tests (platform gate, no-op on non-win32) +- `packages/consolonia/package.json` — version 0.7.2, added koffi optional dependency +- `packages/cli/package.json` — version 0.7.2 +- `packages/recall/package.json` — version 0.7.2 +- `.teammates/settings.json` — cliVersion 0.7.2 +- `package-lock.json` — updated by npm install +- `.teammates/beacon/memory/2026-03-30.md` + +### Next +- Restart the running `teammates` CLI to pick up the new code. The user should test mouse clicks in both terminals. + +## Task: Re-audit win32-console.ts against external Consolonia — fix ENABLE_MOUSE_INPUT conflict +User reported mouse still not working. Re-compared our `win32-console.ts` against the external C#/.NET Consolonia at `C:\source\consolonia`. Found a critical architectural mismatch. + +### Key findings +- **External Consolonia uses `ReadConsoleInput()` for mouse on Windows** — it reads raw `MOUSE_EVENT_RECORD` structures via Win32 API. It does NOT send ANY ANSI escape sequences for mouse tracking on Windows. The ANSI mouse codes (`?1000h`, `?1006h`) are only used in the Unix/Curses path. +- **Node.js/libuv drops `MOUSE_EVENT` records** — libuv calls `ReadConsoleInputW` internally but only processes `KEY_EVENT` records for stdin. Mouse events are silently discarded. This means `ENABLE_MOUSE_INPUT` does nothing useful for us. +- **`ENABLE_MOUSE_INPUT` may actively interfere** — by enabling this flag, we're telling the console subsystem to route mouse events into the INPUT_RECORD buffer (where Node.js drops them), potentially preventing the terminal emulator from translating them into VT escape sequences. +- Our VT mouse approach (DECSET sequences → VT mouse sequences on stdin) depends on the terminal emulator (Windows Terminal, VS Code xterm.js) handling mouse at its own layer. The console subsystem flags should not interfere with that. + +### Key decisions +- **Removed `ENABLE_MOUSE_INPUT`** from our `SetConsoleMode` call — explicitly clear it instead of setting it. +- **Force `ENABLE_VIRTUAL_TERMINAL_INPUT` on** — don't just preserve it from the original mode, always set it. This ensures the terminal can deliver VT mouse sequences. +- **Added `console.error` diagnostics** to every failure path in `enableWin32Mouse()` so silent failures become visible. +- All 602 consolonia tests still pass. + +### Files changed +- `packages/consolonia/src/ansi/win32-console.ts` — removed ENABLE_MOUSE_INPUT, forced VT input, added error logging +- `.teammates/beacon/memory/2026-03-30.md` +- `.teammates/beacon/memory/decision_win32_console_mode.md` (updated) + +## Task: Rebuild and bump version to 0.7.3 +Clean rebuild of all three packages with version bump from 0.7.2 → 0.7.3. + +### Key decisions +- Cleaned all `dist/` folders before rebuilding. +- All three packages compiled cleanly with `tsc`. +- Biome lint auto-fixed 1 file (`win32-console.ts`), rebuilt consolonia after. +- All 1000 tests pass (602 consolonia + 94 recall + 304 cli). +- Updated `package-lock.json` for version consistency. + +### Files changed +- `packages/consolonia/package.json` — version 0.7.3 +- `packages/cli/package.json` — version 0.7.3 +- `packages/recall/package.json` — version 0.7.3 +- `.teammates/settings.json` — cliVersion 0.7.3 +- `package-lock.json` — updated versions +- `.teammates/beacon/memory/2026-03-30.md` + +## Task: Remove Win32 SetConsoleMode — go pure ANSI for mouse +Consolonia creator (user's customer) advised that ANSI escape sequences are more accurate for mouse tracking. Removed the entire koffi/Win32 `SetConsoleMode` approach and now rely purely on ANSI DECSET mouse sequences. + +### Key decisions +- **Deleted `win32-console.ts`** — the koffi FFI module that called `kernel32.dll SetConsoleMode`. This was likely interfering with how Windows Terminal delivers VT mouse sequences by manipulating console mode flags at the wrong layer. +- **Removed koffi** from `optionalDependencies` — no more native compilation dependency. +- **Removed all Win32 console mode calls** from `App._setup()` and `App._teardown()`. +- **Removed exports** from `index.ts` and tests from `ansi.test.ts` (4 tests removed). +- The ANSI mouse escape sequences (`?1000h`, `?1003h`, `?1005h`, `?1006h`, `?1015h`, `?1016h`) in `esc.ts` are unchanged — these are the correct mechanism. +- Build clean, 598 consolonia tests pass. + +### Files changed +- `packages/consolonia/src/ansi/win32-console.ts` — **deleted** +- `packages/consolonia/src/app.ts` — removed win32-console imports and calls +- `packages/consolonia/src/index.ts` — removed win32-console exports +- `packages/consolonia/src/__tests__/ansi.test.ts` — removed win32-console tests +- `packages/consolonia/package.json` — removed koffi from optionalDependencies +- `package-lock.json` — updated +- `.teammates/beacon/memory/2026-03-30.md` + +## Task: Add /about diagnostic command +Added a `/about` slash command (aliases: `/info`, `/diag`) that displays comprehensive diagnostic info and copies it to the clipboard. + +### Key decisions +- Reports: version, Node.js version, platform/arch, OS, terminal name/caps (from `detectTerminal()`), terminal dimensions, env vars (TERM, TERM_PROGRAM, WT_SESSION on Windows), all capability flags, adapter name, teammate list, services, GitHub CLI version (if installed), and internal state (active tasks, queued tasks, threads, conversation length). +- Reuses existing `doCopy()` to copy everything to clipboard automatically. +- Uses styled output: muted labels, regular values, bold title. +- Added `execSync` import (merged with existing `exec as execCb`) for `gh --version`. +- Added `detectTerminal` import from `@teammates/consolonia`. +- All 304 CLI tests pass. Clean build with lint. + +### Files changed +- `packages/cli/src/commands.ts` — added `/about` command registration and `cmdAbout()` implementation +- `.teammates/beacon/memory/2026-03-30.md` + +## Task: Replace Unicode emoji symbols with ASCII to fix terminal tracers +User reported rendering tracers (ghost characters) in the `/about` diagnostic output caused by Unicode dingbat characters (`✔`, `✖`, `⚠`, `✓`, `ℹ`) having ambiguous width — some terminals treat them as double-width but the buffer assumes single-width. + +### Key decisions +- Replaced all Unicode status symbols with ASCII equivalents across the entire CLI package: `✔`/`✓` → `+`, `✖` → `x`, `⚠` → `!`, `ℹ` → `-`. +- In `/about` capability flags, changed `✔`/`✖` to `yes`/`no` for clarity. +- Left braille spinner characters (`⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏`) alone — they are single-width and don't cause tracers. +- Left consolonia test file's `✔` reference alone — it's testing width calculation, not displayed to users. +- All 304 CLI tests pass after changes. + +### Files changed +- `packages/cli/src/commands.ts` — replaced `✔`, `✖`, `⚠` with ASCII +- `packages/cli/src/cli.ts` — replaced `✖` with `x` +- `packages/cli/src/feed-renderer.ts` — replaced `⚠` with `!` +- `packages/cli/src/handoff-manager.ts` — replaced `✖`, `⚠` with ASCII +- `packages/cli/src/onboard-flow.ts` — replaced `✔`, `⚠` with ASCII +- `packages/cli/src/service-config.ts` — replaced `✓` with `+` +- `packages/cli/src/startup-manager.ts` — replaced `✔`, `✖`, `ℹ` with ASCII +- `.teammates/beacon/memory/2026-03-30.md` + +## Task: Fix charWidth for text-presentation symbols — restore emojis +User wanted emojis back, not ASCII replacements. Root cause: `charWidth()` treated entire Miscellaneous Symbols (0x2600-0x26FF) and Dingbats (0x2702-0x27B0) ranges as width 2, but characters like ✔ ✖ ⚠ ✓ ℹ have text presentation by default and render as 1 cell in terminals. The buffer allocated 2 cells (with continuation marker), but the terminal only used 1 — the continuation cell appeared as a "tracer" ghost character. + +### Key decisions +- **Fixed `charWidth()` in `symbol.ts`** — replaced the two broad emoji ranges with specific per-character/subrange checks for only the characters that have `Emoji_Presentation=Yes` in Unicode (e.g. ☔☕✅❌❗⚽⛽ etc.). Characters with text presentation (✔ ✖ ⚠ ✓ ℹ ✂ © ®) now correctly return width 1. +- Also removed other overly broad ranges: media controls (⏸-⏺), play/reverse buttons (▶◀), copyright/registered (©®), supplemental arrows, wavy dash — none have `Emoji_Presentation=Yes`. +- **Reverted all ASCII replacements** across 7 CLI source files back to original Unicode symbols. +- Updated consolonia `styled.test.ts` to expect width 1 for ✔ (was 2). +- Added 16 new tests in `pixel.test.ts`: 8 for text-presentation chars (width 1) and 8 for emoji-presentation chars (width 2). +- All 600 consolonia + 304 CLI tests pass. + +### Files changed +- `packages/consolonia/src/pixel/symbol.ts` — narrowed emoji width ranges to Emoji_Presentation=Yes only +- `packages/consolonia/src/__tests__/pixel.test.ts` — added text/emoji presentation width tests +- `packages/consolonia/src/__tests__/styled.test.ts` — updated ✔ width expectation from 2 to 1 +- `packages/cli/src/cli.ts` — restored ✖ symbols +- `packages/cli/src/commands.ts` — restored ✔ ✖ ⚠ symbols +- `packages/cli/src/feed-renderer.ts` — restored ⚠ symbols +- `packages/cli/src/handoff-manager.ts` — restored ✖ ⚠ symbols +- `packages/cli/src/onboard-flow.ts` — restored ✔ ⚠ symbols +- `packages/cli/src/service-config.ts` — restored ✓ symbols +- `packages/cli/src/startup-manager.ts` — restored ✔ ✖ ℹ symbols +- `.teammates/beacon/memory/2026-03-30.md` + +## Task: Add Windows Terminal wide overrides to charWidth from user screenshots +User screenshotted the dingbat test list and circled (red boxes) 17 characters that have tracers on Windows Terminal. These are text-presentation characters that Windows Terminal renders as double-width. Added them to `charWidth()` as a separate override section. + +### Key decisions +- Added 17 characters to width 2 in a new "Windows Terminal wide" section in `charWidth()`, separate from the Emoji_Presentation=Yes entries. +- Characters added: ℹ ★ ☆ ♠ ♣ ♥ ♦ ⚐ ⚑ ⚙ ⚠ ✔ ✖ ➜ ➤ ▶ ⏱ +- ⏳ and ⚡ were already width 2 (Emoji_Presentation=Yes) — no change needed. +- Updated pixel.test.ts: split "text-presentation" test into chars that remain width 1 (✓ ✂ © ®) and new "Windows Terminal wide" test for the 17 chars. +- Updated styled.test.ts: ✔ is now width 2, so `"✔ done"` span length changed from 6 to 7. +- All 601 consolonia tests pass. + +### Files changed +- `packages/consolonia/src/pixel/symbol.ts` — added 17 Windows Terminal wide overrides +- `packages/consolonia/src/__tests__/pixel.test.ts` — updated text-presentation test, added Windows Terminal wide test +- `packages/consolonia/src/__tests__/styled.test.ts` — updated ✔ width expectation from 1 to 2 +- `.teammates/beacon/memory/2026-03-30.md` diff --git a/.teammates/beacon/memory/2026-03-31.md b/.teammates/beacon/memory/2026-03-31.md new file mode 100644 index 0000000..8d7c3ec --- /dev/null +++ b/.teammates/beacon/memory/2026-03-31.md @@ -0,0 +1,22 @@ +--- +type: daily +compressed: true +--- +# 2026-03-31 + +## Task: Strip version frontmatter from all teammate memory files +Ran a script to strip `version:` YAML frontmatter from all 99 `.teammates/*/memory/**/*.md` files across all 5 teammates. First pass incorrectly removed entire frontmatter blocks; second pass fixed to only strip the `version:` line. +- Also removed `version:` emission from `compact.ts` and `adapter.ts`, cleaned unused `PKG_VERSION` imports +- Files: `packages/cli/src/compact.ts`, `packages/cli/src/adapter.ts`, all `.teammates/*/memory/**/*.md` + +## Task: Update migration guide to strip version: from memory files +Added `## 0.7.3` migration section to `MIGRATIONS.md` instructing agents to strip `version:` lines. Cleaned stale `version:` references in 0.6.0 and 0.7.0 sections. +- Files: `packages/cli/MIGRATIONS.md` + +## Task: Bump package versions to 0.7.4 +Updated all three packages and settings from 0.7.3 → 0.7.4. +- Files: `packages/cli/package.json`, `packages/recall/package.json`, `packages/consolonia/package.json`, `.teammates/settings.json`, `package-lock.json` + +## Task: Fix CI test failures — cross-platform path.basename +PR #19 failing on Linux: `extractFileFromCommand()` used `path.basename()` on Windows backslash paths, which doesn't work on Linux. Fixed by normalizing backslashes to forward slashes before `basename()`. Commit `6a4b742`. +- Files: `packages/cli/src/activity-watcher.ts` diff --git a/.teammates/beacon/memory/2026-04-01.md b/.teammates/beacon/memory/2026-04-01.md new file mode 100644 index 0000000..babc7c1 --- /dev/null +++ b/.teammates/beacon/memory/2026-04-01.md @@ -0,0 +1,31 @@ +--- +type: daily +--- + +# 2026-04-01 + +## Task: Bump to 0.8.0 + user task logging + user twin compaction +Bumped all package versions from 0.7.4 → 0.8.0. Added user task logging to the user's twin daily memory so orchestration activity is tracked. Added the user's twin to the compaction/compression pipeline. + +### Key decisions +- Created `user-task-logger.ts` module with `logUserTask()` — writes task entries to `.teammates//memory/YYYY-MM-DD.md` after each task completes +- For tasks delegated to other teammates: logs just the assignment (who + what) +- For tasks routed to the user's own avatar (coding agent): includes result summary and changed files +- Logging is fire-and-forget (`.catch(() => {})`) to never block task flow +- Updated `startup-manager.ts` to include user's twin in compaction, daily compression, and stale daily purge +- Compression tasks for the user's twin are routed through `selfName` (the user's avatar, i.e. the coding agent) +- Updated `/compact` command in `commands.ts` to include user's twin when compacting everyone +- Added `userAlias` to `StartupManagerDeps` interface for access in startup maintenance +- 6 new tests in `user-task-logger.test.ts` covering: new log creation, appending, result details, truncation, directory creation + +### Files changed +- `packages/cli/package.json` — version 0.8.0 +- `packages/recall/package.json` — version 0.8.0 +- `packages/consolonia/package.json` — version 0.8.0 +- `.teammates/settings.json` — cliVersion 0.8.0 +- `package-lock.json` — updated versions +- `packages/cli/src/user-task-logger.ts` — **new** — user task logging module +- `packages/cli/src/user-task-logger.test.ts` — **new** — tests for user task logging +- `packages/cli/src/cli.ts` — import + call logUserTask after task completion in drainAgentQueue +- `packages/cli/src/startup-manager.ts` — added userAlias to deps, include user twin in compaction/compression/purge +- `packages/cli/src/commands.ts` — include user twin in /compact command diff --git a/.teammates/beacon/memory/2026-04-02.md b/.teammates/beacon/memory/2026-04-02.md new file mode 100644 index 0000000..ca23adf --- /dev/null +++ b/.teammates/beacon/memory/2026-04-02.md @@ -0,0 +1,218 @@ +--- +type: daily +--- + +# 2026-04-02 + +## Task: Analyze OpenClaw vs Teammates memory comparison doc +Read `docs/OpenClaw_vs_Teammates_Memory_Comparison.docx` (prepared by Scribe) and identified which OpenClaw features are worth porting to Teammates. + +### Key decisions +- Research-only task; no code changes. +- Identified 4 high-value features from OpenClaw worth adding: MMR re-ranking, temporal decay, embedding cache, file watching. +- Identified 2 medium-value features: memory flush before compaction, session transcript indexing. +- Ruled out features that conflict with zero-cloud principle (multiple embedding providers, batch embedding APIs). + +### Files changed +- `.teammates/beacon/memory/2026-04-02.md` — created daily log + +## Task: Spec out Recall v2 features +Wrote detailed implementation specs for 5 features identified from the OpenClaw comparison. User confirmed embedding cache is skipped (we generate embeddings locally, full rebuilds basically never happen). + +### Key decisions +- **Temporal Decay:** Post-retrieval score multiplier using `2^(-age/halfLife)`. Per-content-type half-lives (daily=14d, weekly=30d, monthly=90d, typed_memory=60d). Exempt Pass 1 recency results. New `temporal.ts` module in recall. +- **MMR Re-Ranking:** Greedy selection with diversity penalty. λ=0.7 default. Re-embeds result texts for inter-document similarity (~1s for 20 results). New `mmr.ts` module in recall. +- **File Watching (library API):** New `IndexWatcher` class (EventEmitter) wrapping the existing `node:fs.watch` pattern from `cmdWatch`. Enables CLI orchestrator to keep index warm in background. New `watcher.ts` module in recall. +- **Memory Flush Before Compaction:** LLM-prompted knowledge extraction before `preDispatchCompress()`. Runs as system task, parses fenced `memory` blocks from response, writes typed memories. 30-min cooldown, 30s timeout, gated behind entry threshold. New `memory-flush.ts` module in cli. +- **Session Transcript Indexing:** Opt-in (`indexTranscripts: false` default). Writes capped transcript files to `.teammates//transcripts/`. Auto-purge after 30 days. New `transcript-writer.ts` module in cli, `classifyUri` extended in recall. +- Build order: temporal decay → MMR → file watching → memory flush → session transcripts. + +### Files changed +- `.teammates/beacon/memory/spec_recall_v2_features.md` — **new** — full implementation specs for all 5 features + +## Task: Investigate system prompt splitting for coding agents +User asked whether the debug prompt file (already written to `.teammates/.tmp/debug/`) can be passed as the system prompt to coding agents instead of piping everything as one blob via stdin. + +### Key decisions +- Research-only task; no code changes. +- **Claude supports `--system-prompt-file`** — confirmed via `--bare` help docs. This allows splitting the prompt into system context (identity, wisdom, team, daily logs, instructions) and user message (conversation history + task). +- **Codex, Copilot, Aider have no system prompt flag** — they must keep the current single-blob approach. +- Recommended split: system prompt = everything except `` and ``; user message = handoff context + task. +- Benefits: Anthropic prompt caching, better model attention on the task, may fix the empty-response issue from bloated prompts. +- Implementation plan: modify `buildTeammatePrompt()` to return split parts, add `supportsSystemPromptFile` to `AgentPreset`, update Claude preset to use `--system-prompt-file`. + +### Files changed +- `.teammates/beacon/memory/2026-04-02.md` — updated daily log + +## Task: Implement system prompt splitting for Claude +Split `buildTeammatePrompt()` to return a `PromptParts` object with `systemPrompt`, `userMessage`, and `fullPrompt`. Claude now receives identity/wisdom/daily logs/instructions via `--append-system-prompt-file` and only the task+handoff context via stdin. Other agents (Codex, Copilot, Aider) continue using the single combined prompt via stdin. + +### Key decisions +- **`buildTeammatePrompt()` returns `PromptParts`** instead of `string` — has `fullPrompt` (combined, backward compat), `systemPrompt` (identity through instructions), and `userMessage` (handoff context + task). +- **Used `--append-system-prompt-file`** not `--system-prompt-file` — append preserves Claude Code's built-in system prompt (tool usage instructions, etc.) while adding teammate context on top. +- **Split boundary:** system prompt = ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``. User message = `` + ``. +- **System prompt file** written to `.teammates/.tmp/debug/--system.md` alongside the existing full prompt dump. +- **`AgentPreset` extended** with `supportsSystemPromptFile` boolean flag. +- **Benefits:** Anthropic prompt caching on the system prompt (identity/wisdom/logs are stable across turns), better model attention on the actual task, smaller stdin payload. +- All 312 CLI tests pass including 2 new tests for the `PromptParts` split. + +### Files changed +- `packages/cli/src/adapter.ts` — `buildTeammatePrompt()` returns `PromptParts`, added `PromptParts` interface +- `packages/cli/src/adapters/cli-proxy.ts` — captures `promptParts`, writes `-system.md` file, passes `systemPromptFile` to `buildArgs` and `userMessage` to stdin +- `packages/cli/src/adapters/presets.ts` — Claude preset uses `--append-system-prompt-file`, added `supportsSystemPromptFile: true` +- `packages/cli/src/adapters/echo.ts` — updated to use `.fullPrompt` from `buildTeammatePrompt()` +- `packages/cli/src/index.ts` — exported `PromptParts` type +- `packages/cli/src/adapter.test.ts` — updated all tests for `PromptParts` return type, added 2 new tests for system/user split + +## Task: Reorganize prompt pipeline — static SYSTEM-PROMPT.md + chunked recall + new budget system +Major restructuring of how prompts are built and delivered to coding agents. Three changes: + +### 1. Static SYSTEM-PROMPT.md per teammate +Created `system-prompt.ts` module with `generateSystemPrompt()` that builds a static SYSTEM-PROMPT.md file for each teammate. Contains all stable prompt sections (IDENTITY, GOALS, WISDOM, TEAM, SERVICES, RECALL_TOOL, ENVIRONMENT, USER_PROFILE, INSTRUCTIONS). Generated at startup via `writeAllSystemPrompts()`, hooked into `StartupManager.startupMaintenance()`. Claude uses this file directly via `--append-system-prompt-file` instead of writing a temp file each task. + +### 2. Chunked recall indexing +Created `chunker.ts` in recall with `chunkMarkdown()` that splits memory files into ~2k token chunks (~8k chars). Splits on markdown headings first, then paragraph boundaries. Each chunk is indexed as a separate Vectra document with URI suffix `#0`, `#1`, etc. Updated `Indexer.indexTeammate()`, `syncTeammate()`, and `upsertFile()` to use chunking. Search results now include `partial` and `period` metadata fields. Added `extractPeriod()`, `isChunkUri()`, `uriToRelativePath()` helpers. + +### 3. New 20k token budget system +Replaced the old dual-budget system (12k daily logs + 8k recall) with a unified 20k token budget for the user message. Priority order: +1. User's message (unbounded, always included) +2. Conversation history (highest budget priority) +3. Daily log snapshot from before conversation started (medium) +4. Recalled memories in MEMORY: format (lowest) + +As conversation grows, it naturally pushes out recall and daily logs. New `buildUserMessage()` function handles the budget allocation. `buildTeammatePrompt()` refactored to use pre-built system prompt content + `buildUserMessage()`. Recalled memories formatted as: +``` +MEMORY: +file: +type: +period: