ai-advisory-board 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # CHANGELOG — AI Advisory Board CLI
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - cc1521a: Knowledge & discussion UI improvements:
8
+
9
+ - **Multi-source ingest** — the web UI Ingest panel now accepts multiple URLs (one per line) plus native file and folder pickers alongside pasted text. Picked files upload their content to the local server (browsers never expose absolute paths), are filtered to ingestible text/doc types with a 20 MB cap, and are ingested sequentially with per-item progress. New `ingestFileBuffer()` core function and a `file: { name, contentBase64 }` variant on `POST /api/knowledge/ingest`.
10
+ - **Markdown rendering in board-member answers** — member responses now render markdown (bold, headings, lists, inline code, code fences, blockquotes, and `[[wikilinks]]`) instead of showing raw markers.
11
+ - **Add action steps to the Action Board from a discussion** — each suggested action step now has a "+ Add" button that creates a linked action item (attributed to the advising member) without leaving the discussion view.
12
+
13
+ ## 0.1.1
14
+
15
+ ### Patch Changes
16
+
17
+ - c216efe: `aab init` now suggests `aab ui` in the "Next steps" output. The web dashboard at `http://127.0.0.1:3737` is the most welcoming entry point for first-time users, so it's now surfaced right after `aab doctor` with a "(recommended)" hint.
18
+
3
19
  A chronological log of meaningful changes. Group by date; sub-section by topic. Each entry lists the user request that triggered it, the files touched, the why, and what was verified live.
4
20
 
5
21
  The format is loosely "Keep a Changelog" but date-grouped — we're not yet versioned. Once we ship `aab@1.0.0`, switch to per-version sections.
@@ -15,6 +31,7 @@ The format is loosely "Keep a Changelog" but date-grouped — we're not yet vers
15
31
  **What:** Closed out all 6 chunks of Phase 6.6 + the cross-cutting items. The dashboard is now (a) MCP-drivable via stable `data-testid` locators per the registry in `docs/development/PLAYWRIGHT_MCP.md` §6, (b) accessible to screen readers (landmarks + live regions + dialog semantics + labelled chips), (c) covered by a checked-in spec library that doubles as the source-of-truth for future `@playwright/test` ports, (d) wired up to a deterministic CI suite that boots a tempdir workspace + mock-claude shim so PR runs don't burn real subscription tokens, (e) gated by a 3-OS × 2-Node × 4-shard GitHub Actions matrix.
16
32
 
17
33
  **Files touched:**
34
+
18
35
  - `gui/index.html` — sidebar nav now carries `data-testid="tab-{route}"` on all 9 nav buttons (was: only 5 had `nav-{route}` testids — renamed for consistency). `<aside aria-label="Navigation sidebar">`, `<nav aria-label="Main">`, `<main role="main" data-testid="main">`. Every decorative emoji span (`nav-icon`, `brand-mark`, `status-dot`, theme-toggle icon) gets `aria-hidden="true"`. `ws-label` is now `role="status" aria-live="polite"`. New-discussion / edit / confirm modals all carry `role="dialog" aria-modal="true" aria-labelledby` (so they're announced as dialogs, not generic divs). Question textarea and Start button gain `new-discussion-question` / `new-discussion-start` testids.
19
36
  - `gui/app.js` — added `memberSlug(name)` helper mirroring `memberAgentSlug()` and `shortIdOf(id)` for the discussion-row testid. Wired the full Phase 6.6 §6 registry into the live DOM: `new-discussion` on the + New discussion button; `new-discussion-member-<slug>` on each chip (chips are now `<button role="checkbox" aria-checked aria-label="Toggle <name>">` — keyboard-reachable + screen-reader-friendly); `discussion-row-<shortId>` on every row card (`role="button" tabindex="0"`); `chat-stream` with `role="log" aria-live="polite" aria-relevant="additions"`; `member-typing-<slug>` on typing bubbles (`role="status" aria-live="polite" aria-label="<member> is thinking"`); `member-message-<slug>-<turn>` on each response card (preserves `data-testid-kind="response-card"` for backward-compat with `docs/specs/sparring-anchor-deepdive.md`); `orchestrator-decision-<round>` on the orchestrator card (`role="status"` + `data-action` for inspection); `discussion-continue` / `discussion-followup-open` / `discussion-followup-input` / `discussion-followup-send` on the chat-footer controls; `hitl-prompt` on the yellow warning bubble (`role="status" aria-live="polite"`); `hitl-panel` on the respond form (`role="dialog" aria-modal="true" aria-labelledby="hitl-reply-heading"`); `hitl-option-<index>` on each option chip (`aria-label="Option <n>: <text>"`); `hitl-reply-input` (`aria-label="Reply to the board"`) + `hitl-reply-submit`; `discussion-concluded` on the concluded marker (`role="status"`). Orchestrator bubble signature gained an explicit `roundNumber` argument; the `addOrchestratorDecision` WS handler now passes `msg.roundNumber` through.
20
37
  - `src/commands/doctor.ts` — two new checks land when `.mcp.json` is present in the agents-dir: (a) `Playwright MCP install` (✗ if `node_modules/@playwright/mcp/cli.js` is missing — hint: `npm install`); (b) `Playwright browsers` (✗ if the platform-appropriate `ms-playwright` cache directory is missing or empty — hint: `npx playwright install`). Both checks are skipped when `.mcp.json` doesn't exist (so users without the MCP wiring aren't bothered).
@@ -27,11 +44,13 @@ The format is loosely "Keep a Changelog" but date-grouped — we're not yet vers
27
44
  - **`package.json`:** `@playwright/test@^1.49` devDep added; new scripts `test:e2e`, `test:e2e:ui`, `test:e2e:install`.
28
45
 
29
46
  **Tests:**
47
+
30
48
  - Full vitest suite: **275/275 passing** unchanged (Phase 6.6 work is UI-side; no vitest fixtures regressed).
31
49
  - Playwright deterministic suite: `AAB_UI_BASE_URL=http://127.0.0.1:3737 AAB_UI_SKIP_SERVER=1 npx playwright test --project=chromium` → **6/6 passing** in 3.3s against the live `smoke-kw-2026-05-19` workspace.
32
50
  - Typecheck: clean. Build: `dist/bin/aab.js` 648.13 KB.
33
51
 
34
52
  **Verified — live Playwright MCP smoke on `~/.aabcli/smoke-kw-2026-05-19` (2026-05-21):**
53
+
35
54
  - **Sidebar landmarks:** snapshot shows `complementary "Navigation sidebar"` (the `<aside>` `aria-label`) and `navigation "Main"` (the `<nav>` `aria-label`). All 9 nav buttons render with only their visible labels — the emoji prefixes are `aria-hidden`, so screen readers don't double-announce. `main` is correctly identified as the document landmark. `status [ref=…]: connected` confirms the `ws-label` live region works.
36
55
  - **Tab testids:** `browser_evaluate` confirmed `[data-testid="tab-discussions"]` through `[data-testid="tab-settings"]` all resolve (all 9 routes). Sidebar `<aside>` carries `aria-label="Navigation sidebar"` and the main landmark carries `role="main"`. Theme toggle exposes `aria-label="Switch to light theme"` (toggles to "Switch to dark theme" on click).
37
56
  - **New-discussion modal:** opening the modal yields `role="dialog" aria-modal="true" aria-labelledby="new-discussion-title"`. Question textarea and Start button resolve via their testids. Member chips: `new-discussion-member-elon-musk` / `new-discussion-member-julian-bent-singh` / `new-discussion-member-alexandra-chen-cfa` all render as `<button role="checkbox" aria-checked="true" aria-label="Toggle <name>">` — pre-selected by default, toggle-able via click, screen-reader-friendly.
@@ -50,6 +69,7 @@ Screenshots: `test-artifacts/p66-discussions-tab.png`, `test-artifacts/p66-chat-
50
69
  **What:** Closed out the four open polish items on Phase 6.5 (per-member color from frontmatter, token-usage dashboard, light theme + toggle, mobile responsive sidebar) and flipped the four scope-reference stubs to ✅ (each was already shipped in its owning Phase 2-5 §UI section — they were leftover index entries from when Phase 6.5 was a unified bucket).
51
70
 
52
71
  **Files touched:**
72
+
53
73
  - `src/agents/emit-member-agent.ts` — added `readMemberAgentColor(name, projectRoot?)` helper. Walks the YAML frontmatter line-by-line (stops at the closing `---` so body-level `color:` text can't poison the result), lowercases the value, validates against the 9-color palette, returns undefined on any miss/error.
54
74
  - `src/gui/server.ts` — `enrichMembers()` / `enrichOne()` now take a `projectRoot` and add `color` to the wire object when the agent file has one. Two callsites updated (`/api/state` + `/api/members`). New endpoint `GET /api/usage[?since=YYYY-MM-DD&limit=N]` that returns `{ since, totalLogs, summary }` after running the pure aggregator. The GUI already does `m.color || colorForMember(m.name)` so the fallback chain just works.
55
75
  - `src/core/tokens/usage-summary.ts` — new pure aggregator that buckets `TokenUsageLog[]` into totals + byDay (ascending) + byFeature/byModel (sorted by cost desc). Falls back to `"unknown"` for missing feature/model/date so a malformed JSONL line never crashes the dashboard. Window start/end auto-derived from the data.
@@ -58,12 +78,14 @@ Screenshots: `test-artifacts/p66-discussions-tab.png`, `test-artifacts/p66-chat-
58
78
  - `gui/style.css` — appended `:root[data-theme="light"]` token overrides (light bg, darker member palette for contrast on white). Added the theme-toggle styles, the floating hamburger + scrim styles, the `@media (max-width: 760px)` mobile block (sidebar slides in from `translateX(-100%)`, view padding adjusts for the floating button) and a complementary `@media (min-width: 761px)` block that force-hides the hamburger so the desktop layout is untouched. Added the entire `.usage-*` family (totals grid, sparkline bars with hover tooltips, table with inline cost-share bars).
59
79
 
60
80
  **Tests (16 new, 275/275 total passing):**
81
+
61
82
  - `src/agents/__tests__/read-member-agent-color.test.ts` (8 tests) — missing file, basic parse, quoted value, case-insensitive match, unknown color rejected, body-level `color:` ignored, no-frontmatter file ignored, parser stops at the closing `---`.
62
83
  - `src/core/tokens/__tests__/usage-summary.test.ts` (8 tests) — empty input gives zeroed totals, totals sum correctly across logs, byDay sorted ascending, byFeature/byModel sorted by cost desc, cache tokens accumulated separately, windowStart/windowEnd track earliest/latest, missing fields fall back to `"unknown"`.
63
84
 
64
85
  **Why these four were the actual Phase 6.5 work:** Per the "Scope clarified 2026-05-19" note in `docs/development/CHECKLIST.md`, Phase 6.5 is the polish + cross-cutting + shipped-views index. The four cross-reference stubs (`Discussion: spar`, `Decision Coach chat view`, `Sparring 1:1 chat view`, `Skill-creator run-launch + telemetry + preflight-wizard UI`) all had their authoritative implementation in Phase 2-5 §UI subsections — every one of those is ✅. The remaining checkboxes were the polish backlog: per-member color from frontmatter, token-usage dashboard, light theme + toggle, mobile responsive sidebar.
65
86
 
66
87
  **Verified — live Playwright MCP smoke on `~/.aabcli/smoke-kw-2026-05-19` (2026-05-21):**
88
+
67
89
  - **Per-member color from frontmatter:** `GET /api/members` returns `Elon Musk → pink`, `Julian Bent Singh → yellow`, `Alexandra Chen, CFA → red` (all sourced from the agent files' `color:` frontmatter, not the deterministic hash). Chat-view DOM verified: `EM` avatar carries `data-color="pink"` — the message bubble for the same member with the deterministic-hash fallback would have rendered a different palette slot.
68
90
  - **Usage dashboard:** sidebar shows `📊 Usage` nav item; clicking it loads totals (`Total cost $0.00`, `Calls 3`, `Total tokens 42.3k`, `Cached read 16.9k`), `Daily spend` sparkline with hover tooltip (`2026-05-19 · $0.00 · 42.3k tokens · 3 calls`), `By feature` table (`discussions 2 · 28.5k`, `sparring 1 · 13.7k`), `By model` table (`sonnet 2 · 28.5k`, `opus 1 · 13.7k`). All four range buttons (`Last 7 days` / `Last 30 days` / `Last 90 days` / `All time`) wired with the `active` class state.
69
91
  - **Light theme + persistence:** toggling cycles `data-theme` between `light` (resolves `--bg: #f8fafc`, `--text: #1a1f29`) and `dark`. The choice persists across page reload via `localStorage["aab-theme"]`.
@@ -83,16 +105,19 @@ Screenshots: `test-artifacts/p65-usage-dark.png`, `test-artifacts/p65-usage-ligh
83
105
  **Spec:** `docs/development/SKILL_CREATOR.md` §6.3 rewritten with the full 9-tier shape, three-pass recon-prompt instructions, anti-bias check, downstream-impact analysis (brief truncation order + Planner directive + validator gate).
84
106
 
85
107
  **Engine — Chunk 1 (schema + recon-prompt extension):**
108
+
86
109
  - `src/core/skill/recon/wiki-recon.ts` — `WikiContext` grows 4 new top-level fields: `playbooks: WikiPlaybook[]` (FULL bodies + confidence), `templates: WikiTemplate[]` (FULL bodies + optional exampleOutput), `domainKnowledge: WikiDomainKnowledge[]` (summary + excerpt), `pastLessons: WikiPastLesson[]` (summary + actionable rule). Maxturns bumped 8 → 12 (recon agent now opens full bodies). New `PROMPT_TEMPLATE` with three-pass instructions (tier classification → open Tier 1 bodies in full → extract Tier 2-3 by summary) + explicit anti-bias check ("do not over-weight stakeholder extraction — most pages in a healthy wiki are about procedures, templates, and concepts, not humans"). Synonym tolerance: `procedures`/`processes` → `playbooks`; `formats`/`examples` → `templates`; `knowledge`/`facts` → `domainKnowledge`; `lessons`/`learnings` → `pastLessons`. Dedupe by slug across canonical + synonym fields.
87
110
  - `src/core/skill/recon/orchestrator.ts` — extended degraded-mode shape + the `onPhaseDone` summary now reports knowledge-tier counts ("3 playbooks, 1 template, 5 knowledge, …") so the GUI's planner-progress-pane surfaces the new tier signal too.
88
111
 
89
112
  **Engine — Chunk 2 (Planner reasoning + brief assembly):**
113
+
90
114
  - `src/core/prompts/skill-planner.ts` — new ~30-line directive added to `<orchestration_directives>` explaining that wiki Tier 1 is "the most load-bearing signal in the whole recon" and giving per-tier execution rules. Includes the explicit validation-gate warning so the model knows the schema will reject if Tier 1 is populated but uncited.
91
115
  - `src/core/parsing/llm-response-schemas.ts` — `validateProposalSemantics` grows a `WikiKnowledgeSlugs` parameter and a new citation gate. Two sub-checks: (a) if any Tier 1 slot has slugs, the proposal's `valueRationale` must cite at least one of them; (b) every playbook slug must appear somewhere meaningful (`valueRationale` OR `proposedWorkflow` OR an integration). Failure surfaces a clean error like "wiki playbook(s) ignored entirely: our-launch-playbook. Playbooks are the most load-bearing wiki tier."
92
116
  - `src/core/skill/planner.ts` — wires the wiki slug arrays into the validation call so the gate fires automatically.
93
117
  - `src/core/skill/build-brief.ts` — new `WikiKnowledgeBundle` field that carries FULL bodies of playbooks + templates to skill-creator. Updated truncation priority order (drops in this order: web innovations → integration citations → narrative edits → domainKnowledge excerpts → template bodies trimmed to 1500 chars → playbook bodies trimmed to 3000 chars as last resort). New `wikiKnowledgeIsBakeIn` constraint added to `DEFAULT_CONSTRAINTS`: tells skill-creator that the wiki bundle is "the user's OPERATING BRAIN, not background hints" — playbook bodies must be quoted verbatim, template bodies are the output shape, domain knowledge inlined where it informs decisions, past-lesson actionables surface as MUST NOT or preflight, every wiki entry cited by slug.
94
118
 
95
119
  **Bugs caught + fixed during the real-Claude verification cascade** (5 attempts total, each catching a different schema-too-strict bug — Opus runs vary field names every time):
120
+
96
121
  1. **`touchpointKind` enum too narrow** — Opus emitted `draft-slack-message`; my enum was `draft-email | slack-mention | calendar-invite | doc-share | other`. Fix: drop the enum, accept any string (this field is display-only — downstream code never switches on it).
97
122
  2. **`integrations: Required`** — Opus put the integration list under `proposalIntegrations` or nested it in `tiers.maximalist.integrations`. Fix: add top-level synonym remap.
98
123
  3. **`skillSummary: Required`** — Opus put the summary under `summary` or `description`. Fix: synonym remap.
@@ -101,19 +126,20 @@ Screenshots: `test-artifacts/p65-usage-dark.png`, `test-artifacts/p65-usage-ligh
101
126
 
102
127
  **Before / after diff — definitive proof the wiki is now load-bearing:**
103
128
 
104
- | Same action (Ship Q3 launch YouTube video distribution pipeline) | Empty wiki | Seeded wiki |
105
- |---|---|---|
106
- | `wiki/` references in emitted SKILL.md | **0** | **24** |
107
- | Wiki full-body files shipped in `references/` | none | 2 (playbook + template) |
108
- | CTA copy in skill body | generic "Start your trial" | **verbatim:** "Start your 7-day free trial — no credit card required. Link in the description." |
109
- | MUST NOT vetoes | generic best-practice (no iframe, no LinkedIn URL, etc.) | 10 vetoes, every one cites the wiki page it came from — Opus pulled A/B-test statistics directly from the wiki body ("38% lower conversion", "23% lower watch completion") and made them mandatory rules |
110
- | Step rationale | generic | cites Phase numbers from the playbook ("Phase 5, step 1–2", "ENDORSED DIRECTION: Slack-only communication") |
111
- | Preamble | minimal | new "## Wiki Sources Baked Into This Skill" section listing both pages with their full text linked into `references/` |
112
- | Wall-clock | 11m 59s | 10m 56s |
129
+ | Same action (Ship Q3 launch YouTube video distribution pipeline) | Empty wiki | Seeded wiki |
130
+ | ---------------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
131
+ | `wiki/` references in emitted SKILL.md | **0** | **24** |
132
+ | Wiki full-body files shipped in `references/` | none | 2 (playbook + template) |
133
+ | CTA copy in skill body | generic "Start your trial" | **verbatim:** "Start your 7-day free trial — no credit card required. Link in the description." |
134
+ | MUST NOT vetoes | generic best-practice (no iframe, no LinkedIn URL, etc.) | 10 vetoes, every one cites the wiki page it came from — Opus pulled A/B-test statistics directly from the wiki body ("38% lower conversion", "23% lower watch completion") and made them mandatory rules |
135
+ | Step rationale | generic | cites Phase numbers from the playbook ("Phase 5, step 1–2", "ENDORSED DIRECTION: Slack-only communication") |
136
+ | Preamble | minimal | new "## Wiki Sources Baked Into This Skill" section listing both pages with their full text linked into `references/` |
137
+ | Wall-clock | 11m 59s | 10m 56s |
113
138
 
114
139
  The emitted skill body opens with a "Wiki Sources Baked Into This Skill" section that names both wiki pages by slug + path to their full text in `references/`. The preflight section quotes the playbook's discipline rules ("Never skip or reorder the 5 phases. Lock the script before visual work begins. Accept no creative revisions after Day 11's single consolidated note pass.") and ABORTs execution if any gate is unmet. The MUST NOT section embeds the wiki's anti-patterns as enforceable rules. Each integration step cites the playbook's Phase + step number it's executing. The skill is the user's playbook, in executable form.
115
140
 
116
141
  **Tests:** 16 new vitest tests bringing the suite to **259/259 passing** (was 244). New coverage spans:
142
+
117
143
  - Wiki recon: 7 tests for Tier 1 parsing (playbooks confidence + verbatim body, templates with optional exampleOutput, domainKnowledge + pastLessons, synonym remap, body-required guard, dedup-by-slug, default-confidence fallback).
118
144
  - Brief assembly: 3 tests for the `wikiKnowledgeIsBakeIn` constraint surface + FULL-body propagation through `buildSkillCreatorBrief` + truncation order that preserves playbooks to the very end.
119
145
  - Schema validator: 4 tests for the wiki citation gate (positive + negative + playbook-in-workflow-counts-as-cited + backwards-compat no-op when omitted).
@@ -163,6 +189,7 @@ The emitted skill body opens with a "Wiki Sources Baked Into This Skill" section
163
189
  **What:** Ran the live Playwright MCP smoke against `aab ui` in the external test folder (per CLAUDE.md §Verification — UI changes in `gui/` or `src/gui/server.ts` mandate a Playwright MCP smoke). Verified the Skills tab + skill detail modal + Action Board Plan/Solve buttons + the Planner progress pane streaming real `planner_recon_progress` WS events (PC scan: 35 apps + 6 CLI tools live-scanned on the test machine; wiki recon + web research completed via real Sonnet calls; live stream populated with 3 phase summaries: `pc-scan: 35 apps, 6 CLI tools, 0 MCP, 0 env` / `wiki-recon: 0 pages, 0 stakeholders, 0 vetoes` / `web-research: 5 patterns, 5 tools, 0 app surfaces`). The proposal modal renders all sections correctly (verified via simulated `planner_proposal_ready` event with a realistic SkillDesignProposal — 3 integration rows spanning 3 source types, 1 stakeholder row, tier radio with maximalist pre-checked, cost line `$2.20 · ~8 min`, all 3 action buttons visible). Re-plan modal opens; 10-char feedback guard works (toast: "Feedback must be at least 10 characters."); close button dismisses cleanly.
164
190
 
165
191
  **Bug caught + fixed via the smoke:** `planner_failed` events surfaced a toast that auto-dismissed after 4.5s and `hidePlannerProgress()`'d the progress modal — after a 10+ min Opus wait the user was left with no proof of failure. Fix in `gui/app.js`:
192
+
166
193
  - Keep the progress modal open on `planner_failed`.
167
194
  - Mark the reasoning phase `data-status="failed"` (red-tinted CSS via the new `.planner-phase[data-status="failed"]` rule).
168
195
  - Render a sticky `<div class="planner-error-banner" data-testid="planner-error-banner">` inside the pane with the error message verbatim.
@@ -172,6 +199,7 @@ The emitted skill body opens with a "Wiki Sources Baked Into This Skill" section
172
199
  **Files changed:** `gui/app.js` (rewrote the `aab-planner-event` failure handlers + added `showPlannerError()`), `gui/style.css` (added `.planner-phase[data-status="failed"]` + `.planner-error-banner` rules), `docs/development/CHECKLIST.md` (flipped the live MCP smoke item to ✅), `CHANGELOG.md` (this entry).
173
200
 
174
201
  **Verified:**
202
+
175
203
  - Typecheck clean, 236/236 tests still passing.
176
204
  - Live MCP smoke against the running UI server caught the actual bug (transient toast on long-running failures) and the fix verified via simulated event dispatch.
177
205
  - The CLAUDE.md mandate "every meaningful change to `gui/` or `src/gui/server.ts` must be exercised via Playwright MCP before being declared done" is now actually met for Phase 5, not just paid lip service to.
@@ -185,17 +213,20 @@ The emitted skill body opens with a "Wiki Sources Baked Into This Skill" section
185
213
  **What:** All 6 chunks of Phase 5 shipped per the authoritative `docs/development/SKILL_CREATOR.md` spec. The headline feature — `aab actions plan|solve` driven by an agentic Skill Planner that reasons across PC scan + Knowledge Wiki + live web research, then hands a structured proposal to Anthropic's official `skill-creator` skill — is live end-to-end with CLI + GUI + WS + 80 new vitest tests + 8 Playwright MCP regression specs.
186
214
 
187
215
  **Engine — Chunk 1 (skill-creator detection + bootstrap):**
216
+
188
217
  - `src/core/skill/resolve-skill-creator.ts` — scope walker (project → user → plugin) with hand-rolled YAML frontmatter parse for `name:` + `version:`; `resolveSkillCreator()` thin alias; `skillCreatorInstallHint()` surfaces the `/plugin install skill-creator@claude-plugins-official` command (interactive-only per [#38505](https://github.com/anthropics/claude-code/issues/38505)).
189
218
  - `aab init --install-skill-creator` — auto-detects + prints install instructions when missing.
190
219
  - `aab doctor` adds 3 checks: skill-creator presence + PC scan probe (fast, no LLM) + web reachability to anthropic.com (≤1.5s HEAD).
191
220
 
192
221
  **Engine — Chunk 2 (recon: PC + Wiki + Web):**
222
+
193
223
  - `src/core/skill/recon/pc-scan.ts` — read-only inventory: desktop apps (Windows registry/Programs/Applications walk; macOS `/Applications`; Linux `.desktop` files), CLI tools (`where`/`which` + cheap `--version` probe across 60 candidates), MCP servers (parses `.mcp.json` at project + user + global scope), browser extensions (Chrome/Edge/Firefox manifest.json walk), env-var allowlist (80+ patterns for `STRIPE_*, HUBSPOT_*, …`), Claude-for-Chrome auth heuristic, computer-use availability heuristic. Pure function: `scan({ projectRoot, envOverride })` for unit-testability. Hard rule: never writes, never hits the network.
194
224
  - `src/core/skill/recon/wiki-recon.ts` — one Sonnet call with `Read/Grep/Glob/maxTurns:8`; recon-specific prompt tuned for stakeholder + decision + veto extraction (NOT the generic `aab knowledge query` prompt); dual-path role extraction (frontmatter `role:` if present, body-paragraph extraction otherwise) since Phase 1.5's entity frontmatter doesn't carry `role:` natively. Returns structured `WikiContext` with `relevantPages` + `stakeholders` + `endorsedDirections` + `vetoes` + `pastDecisions`.
195
225
  - `src/core/skill/recon/web-recon.ts` — two-pass design per T1.3: (Pass 1) general task research (`WebSearch + WebFetch + maxTurns:12`); (Pass 2) per-detected-app integration-surface research on the top 5 apps from PC scan, each with `maxTurns:6`. Pass 2 is what makes the maximalist tier actually maximalist — it surfaces "Elgato Teleprompter has a local HTTP API at port 9012 callable via `Bash(curl *)`" rather than generic best-practice patterns. Returns `WebResearchContext` with `appIntegrationSurfaces[]` + `bestPracticePatterns` + `recommendedTools` + `recentInnovations` + `warningsAndPitfalls` + `webPassesCompleted` for degraded-recon visibility.
196
226
  - `src/core/skill/recon/orchestrator.ts` — `Promise.allSettled` over the three recon phases; aggregates warnings into a top-level `warnings[]` slot; emits `planner_recon_progress` + `planner_recon_done` events to a streaming `onPhaseDone` callback that the WS broadcast layer + CLI spinner both consume.
197
227
 
198
228
  **Engine — Chunk 3 (Planner reasoning + user review):**
229
+
199
230
  - `src/core/prompts/skill-planner.ts` — **the most important prompt in the CLI**. Structured per SKILL_CREATOR.md §6.5a: `<role>` + `<skill_operating_model>` (the 14-line "what is a skill" preamble) + `<master_gpt_prompter_hardening>` (reasoning/tool-use/autonomy/self-verification blocks) + `<ambition_directive>` (three-tier framing + hard ≥3 maximalist gate) + `<orchestration_directives>` (per-recon-surface instructions; chrome-extension + computer-use as first-class kinds) + `<invocation_hint_directive>` (5 worked examples spanning all kinds) + `<output_contract>` (JSON-only) + `<input>` (action + recon triple + settings + replan-feedback) + `<few_shot_examples>` (3 condensed examples: Elgato creative-prod + pricing strategic + LinkedIn chrome-extension). Exposed `renderSkillPlannerPrompt({ ... })`.
200
231
  - `src/core/skill/planner.ts` — `runPlanner()` orchestrates the Opus 4.7 reasoning call (`researchModel`, `maxTurns:1`, `allowedTools:[]`); parses against `skillDesignProposalSchema`; runs `validateProposalSemantics` for the hard gates beyond shape (kebab-case skillName, ≥3 integrations spanning ≥2 source types, reserved-name refusal); re-runs once with a stronger nudge injected into `<replan_feedback>` on validation failure; back-fills `requiredTools` from `invocationHint.tools` on success. `projectGrantedTools()` is the pure function the planner-review layer + GUI both use to compute the final `allowed-tools` allowlist from accepted integrations + stakeholders.
201
232
  - `src/core/parsing/llm-response-schemas.ts` — added `skillDesignProposalSchema` with full nested validation (Integration / Stakeholder / Workflow / Warning / Mismatch sub-schemas), `validateProposalSemantics()` for semantic gates, `RESERVED_SKILL_NAMES` set.
@@ -203,6 +234,7 @@ The emitted skill body opens with a "Wiki Sources Baked Into This Skill" section
203
234
  - `aab actions plan <id>` — first-class command (NOT a debug flag) per the spec's "users will want to see the proposal before committing to a solve." Supports `--planner-tier`, `--planner-no-{web,pc-scan,wiki}`, `--out <path>` for markdown export, `--yes` for auto-accept, `--json` for machine-readable.
204
235
 
205
236
  **Engine — Chunk 4 (skill-creator invocation + adapter + install + persist):**
237
+
206
238
  - `src/core/skill/build-brief.ts` — assembles the JSON brief sent as the user message to a headless skill-creator call. Embeds the full Planner proposal verbatim (the brief's core, not a hint). Truncates over 60 KB in priority order: `webResearch.recentInnovations` → integration citations → `userNarrativeEdits` last. `renderUserMessage` wraps the JSON brief in a fenced block + the `SKILL_CREATOR_DONE: <skillName>` completion sentinel.
207
239
  - `src/core/skill/invoke-skill-creator.ts` — `claude -p --append-system-prompt-file <skill-creator/SKILL.md>` with `allowedTools=Write,Edit,Read,Glob,Bash`, `cwd=<runId workspace tempdir>`, 20-min timeout, `outputFormat: 'stream-json'` for live tool-use events. `walkWorkspace` inventories emitted files. `stubSkillCreatorRun` writes a synthetic SKILL.md for offline testing — used by `aab actions solve --stub`.
208
240
  - `src/llm/claude-code-runner.ts` — `RunOptions` gains `appendSystemPromptFile` + explicit `outputFormat` options (streaming auto-engages when `onEvent` is set, but solve callers can force stream-json without a callback).
@@ -213,16 +245,19 @@ The emitted skill body opens with a "Wiki Sources Baked Into This Skill" section
213
245
  - `aab actions solve <id>` — full SKILL_CREATOR.md §5 flag surface: `--no-planner`, `--planner-tier`, `--planner-no-{web,pc-scan,wiki}`, `--skill-name`, `--scope`, `--no-install`, `--budget-cap-usd`, `--stub`, `--yes`.
214
246
 
215
247
  **Engine — Chunk 5 (`aab actions runs` + `aab skills`):**
248
+
216
249
  - `aab actions runs {list,show,export,delete}` — list with shortId + status icon + cost + duration; show pretty-prints metadata + embedded Planner proposal markdown render; export writes the SKILL.md + supporting files + a re-rendered `proposal.md` into a directory (jszip deferred to Phase 5.5 — directory is the v1 contract).
217
250
  - New top-level `aab skills` command in `src/commands/skills.ts`: `list` (enumerates project + user + plugin scopes via the same scope walker), `show` (pretty-prints SKILL.md), `test` (round-trip via `claude -p --append-system-prompt-file`), `uninstall` (archives to `.snapshots/skills/<name>-<ts>/`), `restore` (restores from `.snapshots/skills/`).
218
251
 
219
252
  **Web UI + Server — Chunk 6:**
253
+
220
254
  - `src/gui/server.ts` adds: `POST /api/actions/:id/plan` (returns 202 + planId; runs async, streams via WS; caches the accepted profile in an in-memory `planCache: Map<planId, ResolvedSkillCapabilityProfile>` for `/solve` re-entry); `GET /api/plans/:planId[?as=md]`; `POST /api/plans/:planId/replan` (server-enforced ≥10 char + max-3 cap); `POST /api/actions/:id/solve` (accepts `planId` to reuse cached profile); `GET /api/actions/:id/runs`; `GET /api/skill-runs/:id`; `DELETE /api/skill-runs/:id`; `GET /api/recon/environment` (fast read-only PC scan, no LLM); `GET /api/skills`; `GET /api/skills/:name`. `coerceSolveEventForWs` helper maps `SolveEvent`s to wire-shape WS events with planId/runId stamped at the top level.
221
255
  - `gui/app.js` adds: Plan + Solve buttons on every action card; the Planner progress pane modal (4-phase grid + live tool-call stream, last 20 rows); the proposal modal (tier radio + per-integration toggle rows + per-stakeholder toggle rows + narrative editor textarea + cost line + Accept / Re-plan / Reject / Export-md buttons); the re-plan feedback modal; the run-detail modal (reused from Skills tab); the Skills tab (`renderSkillsView`) with show + test buttons; the `aab-planner-event` browser-event dispatcher for forwarding all `planner_*` and `skill_run_*` WS events to the planner UI.
222
256
  - `gui/index.html` adds: the 🧠 Skills nav item; the planner-progress / proposal / replan-feedback / run-detail modal backdrops with all `data-testid` attributes per spec.
223
257
  - `gui/style.css` adds: `.kanban-card-actions`, `.planner-phase` (color-coded by status), `.planner-stream`, `.planner-proposal` block styles, `.planner-tier-row`, `.planner-rationale`, `.planner-integration-row`, `.planner-stakeholder-row`, `.planner-kind` (mono chip), `.planner-cost`, `.skills-view`, `.skills-row`, `.skill-detail-body`.
224
258
 
225
259
  **Specs (Playwright MCP regression library):**
260
+
226
261
  - `docs/specs/skill-plan-only.md` — Plan button → proposal modal → export-to-md.
227
262
  - `docs/specs/skill-planner-maximalist.md` — Recipe A/D seed → ≥3 integrations across ≥2 surfaces → toggle behavior.
228
263
  - `docs/specs/skill-planner-replan.md` — proposal → Re-plan → feedback ≥10 chars → re-planned proposal mentions feedback keyword.
@@ -237,12 +272,14 @@ The emitted skill body opens with a "Wiki Sources Baked Into This Skill" section
237
272
  **Live smoke:** Stub-mode `aab actions solve d525be59 --no-planner --stub --yes` from the external test folder (`C:\Users\julia\Downloads\kode\ai-advisoryboardclitestfolder`) completed in 315ms end-to-end. Produced a valid SKILL.md at `.claude/skills/phase-5-smoke-action/SKILL.md` with the deterministic `grantedTools` projection (`Read, Write, Glob, Grep`). `actionItem.linkedSkill` populated. `aab actions runs show c47ee06b` renders the full embedded Planner proposal. `aab skills list` enumerates the new skill alongside the stubbed skill-creator. `aab skills uninstall phase-5-smoke-action --yes` archives cleanly to `.snapshots/skills/phase-5-smoke-action-<ts>/`. **`aab doctor` from the same folder passes all 14 checks** including the 3 new Phase 5 checks (skill-creator presence, PC scan probe surfacing platform + cli-tool count, web reachability to anthropic.com in <500ms). Real-Claude end-to-end smoke against `docs/development/SKILL_CREATOR.md` §20a Recipes A/D/E/F deferred to user — each Planner run is ~$2.20 ($1.74 Planner + $0.45 skill-creator typical) — but the orchestrator + brief + adapter + install + persist + WS pipeline is verified to work without burning tokens via the stub path; the only thing real-Claude validates beyond stub is skill-creator's emit quality (which Anthropic's own ~117k weekly-install skill is responsible for, not our bridge code).
238
273
 
239
274
  **Strategic notes:**
275
+
240
276
  - The deliberate reframe from the original sage-council port plan (~5,000 LOC of skill-builder + 14-prompt pipeline) to a thin orchestrator around Anthropic's official skill-creator saved ~85% of the engineering work and redirected the capacity into the agentic Skill Planner — the actual depth-of-feature contribution this CLI makes that doesn't exist in either sage-council or Anthropic's stock skill-creator. Net diff per the spec's §3: ~5,000 LOC removed; ~800 LOC added — actual shipped count is ~1,400 LOC across `src/core/skill/` + the Planner prompt template + the GUI integration.
241
277
  - The depth-of-feature thesis ("Planner reasons about ≥3 multi-tool orchestrations spanning ≥2 distinct surfaces, including first-class `chrome-extension` and `computer-use` invocation kinds") is enforced at three layers: (1) the prompt's `<ambition_directive>` hard gate, (2) the `skillDesignProposalSchema` zod validation, (3) the `validateProposalSemantics` function that runs after schema parse. Failures trigger one automatic re-run with the validation errors injected into `<replan_feedback>`; if that also fails, `ContractError` surfaces with hints pointing at `--planner-tier standard` or wiki/MCP seeding.
242
278
  - The `invocationHint.kind` enum is the load-bearing addition that turns "skills as prompt packs" into "skills as agents" — each integration carries an executable contract (the verbatim snippet for bash/mcp/write, or the user-handoff prose for chrome-extension/computer-use). The brief constraint instructs skill-creator to embed snippets verbatim, not paraphrase.
243
279
  - The two-step Plan → Solve UX (Solve button always goes through Plan first) is deliberate: per the spec, "users will want to see the Planner's proposal before committing to burn ~$2 on skill-creator." Cheap discovery, expensive commitment.
244
280
 
245
281
  **Docs:**
282
+
246
283
  - `docs/development/CHECKLIST.md` — all Phase 5 boxes flipped to ✅; phase emoji flipped to ✅; new ~600-word closeout narrative under "What's running right now"; "Next sensible chunk" pointer advanced to Phase 6.
247
284
  - `docs/development/SKILL_CREATOR.md` — unchanged (it's the authoritative spec; this PR is the implementation).
248
285
  - `CHANGELOG.md` — this entry.
@@ -260,26 +297,31 @@ The emitted skill body opens with a "Wiki Sources Baked Into This Skill" section
260
297
  **What:** Closed the half-finished HITL loop. `aab discuss start` could produce a `pendingUserRequest`, but there was no way to reply or to drive round 2. Now there is.
261
298
 
262
299
  **Engine** (`src/core/discussion/conversation-flow.ts`)
300
+
263
301
  - `continueDiscussion({ discussion, members, settings, storage, ... })` — runs the **pre-round clarification gate** (one orchestrator call) before any model spawn. If the gate returns `request_user_input`, sets `pendingUserRequest`, saves, and returns `{ gated: true }` without burning member tokens. Otherwise generates round N+1, runs post-round orchestrator, persists.
264
302
  - `respondToUserRequest({ discussion, content, selectedOption?, ... })` — appends a `UserResponse{type:'advisory_board_requested'}`, clears `pendingUserRequest`, then calls `continueDiscussion` with `skipPreRoundGate: true` (the orchestrator just asked for this exact reply — re-running it would loop forever) and the user's reply threaded as `userFollowUp.content`.
265
303
  - Bonus: when a discussion concludes via `maxTurns`, any leftover `pendingUserRequest` is cleared so the UI never shows "done" alongside an unanswerable HITL prompt. Same fix in `startDiscussion` for round-1-ends-at-maxTurns.
266
304
 
267
305
  **CLI** (`src/commands/discuss.ts`)
306
+
268
307
  - `aab discuss continue <idOrShort> [--agents-dir <path>]`
269
308
  - `aab discuss respond <idOrShort> <answer> [--option <i>] [--agents-dir <path>]` — `--option` is 1-based, validated against the actual `pendingUserRequest.options[]` list.
270
309
  - Refactored `start`/`continue`/`respond` to share `verifyAgentFiles()` + `progressHandler()` helpers.
271
310
  - Added `.warn()` to the TTY-fallback shim in `src/ui/spinner.ts` so cold-shell mode doesn't crash when we surface a gate decision.
272
311
 
273
312
  **Web UI** (`src/gui/server.ts`, `gui/app.js`, `gui/style.css`)
313
+
274
314
  - `POST /api/discussions/:id/continue` and `/respond` — same 202 + WS-broadcast pattern as `POST /api/discussions`. Returns `409 Conflict` when state forbids the action (already concluded, awaiting input, etc.).
275
315
  - New `discussion_gated` WS event when the pre-round gate stops things short.
276
316
  - Chat view footer now has: a **Continue button** when the discussion is open and not gated; an **inline reply form** (with option chips when the orchestrator listed any) when there's a pending HITL; "✓ Discussion concluded." line when done.
277
317
 
278
318
  **Verified live (May 2026):**
319
+
279
320
  - `start` → 3 members responded → orchestrator gated next round → `respond --option 1` with answer → 3 members responded round 2 → orchestrator asked again → maxTurns auto-concluded.
280
- - Pre-round gate fires *before* any member spawn — confirmed zero member tokens spent when the orchestrator wants user input first.
321
+ - Pre-round gate fires _before_ any member spawn — confirmed zero member tokens spent when the orchestrator wants user input first.
281
322
 
282
323
  **Docs:**
324
+
283
325
  - `docs/development/CHECKLIST.md` — flipped 6 boxes to ✅; rewrote "What's running right now" with the live milestone.
284
326
  - `README.md` — updated "Working today" + commands table; added the gate explanation.
285
327
 
@@ -292,6 +334,7 @@ The emitted skill body opens with a "Wiki Sources Baked Into This Skill" section
292
334
  **What:** Ask one specific board member, a subset of the board, or everyone — without the orchestrator deciding.
293
335
 
294
336
  **Engine** (`src/core/discussion/conversation-flow.ts`)
337
+
295
338
  - `addFollowUpQuestion({ discussion, question, members, targetType, ... })` with `targetType: 'all' | 'specific' | 'subset'`.
296
339
  - Candidate pool restricted to the discussion's original `selectedMemberIds` — a follow-up can never pull in a member the discussion never had.
297
340
  - Pre-round clarification gate fires here too, per PLAN §4.3.1.
@@ -300,10 +343,12 @@ The emitted skill body opens with a "Wiki Sources Baked Into This Skill" section
300
343
  - Exported new `FollowUpTargetType` type.
301
344
 
302
345
  **CLI**
346
+
303
347
  - `aab discuss follow-up <idOrShort> <question> [--all|--member <name>|--members <a,b,c>]`
304
348
  - Mutually exclusive flags. Member token resolution by id, slug, exact name (case-insensitive), or unambiguous prefix. `--members` requires at least 2 distinct members (one is `--member`, all is `--all`).
305
349
 
306
350
  **Web UI**
351
+
307
352
  - `POST /api/discussions/:id/follow-up` — body `{ question, targetType, selectedMemberId?, selectedMemberIds? }`. Validates targetType + selection. Same WS broadcast pipeline.
308
353
  - New chat-footer **Follow up** button. Click opens an inline composer with a textarea + a deselectable member-chip selector. Frontend infers `targetType` from chip count (all selected → `'all'`, exactly 1 → `'specific'`, in between → `'subset'`).
309
354
 
@@ -320,15 +365,18 @@ The emitted skill body opens with a "Wiki Sources Baked Into This Skill" section
320
365
  **Root cause investigation:** Server returned 3 members fine via `/api/state`. The bug was the modal showing empty chips — caused by either (a) workspace resolution drift between `aab init` and `aab ui` cwd, or (b) a fresh modal opening before bootstrap had finished.
321
366
 
322
367
  **Empty-state bug fix** (`gui/app.js`)
368
+
323
369
  - New-discussion modal now shows a **loud yellow warning** when `state.members` is empty: prints workspace ID, full root path in monospace, and explicit instructions ("either run `aab init` here, or click Board members to add one"). Start button is disabled until at least one active member exists.
324
370
  - `openNewDiscussionModal` is now `async` and refreshes state from server before opening — protects against stale state if the user just edited members in another tab.
325
371
 
326
372
  **Workspace transparency** (`gui/index.html`, `gui/app.js`, `gui/style.css`)
373
+
327
374
  - New **workspace card** in the sidebar above the nav with three rows: scope pill (`home`/`project`, color-coded cyan/green), member count (`N/M active`), and the full root path in monospace. Updates whenever members change.
328
375
  - Server `/api/state` now returns `workspace.scope` and `workspace.projectRoot` (used by the card and by future "is this the right workspace?" checks).
329
376
  - Added `getWorkspaceScope()` to `FsStorageService`.
330
377
 
331
378
  **Members CRUD** — fully working from the UI
379
+
332
380
  - Server: `POST /api/members`, `PATCH /api/members/:id`, `DELETE /api/members/:id`. CRUD also touches `.claude/agents/<slug>.md`:
333
381
  - On create: emits the agent file via `emitMemberAgentFile`.
334
382
  - On update: re-emits when name/persona/voice/expertise/tools changed; if name changed, deletes the old slug file (only if AAB-generated).
@@ -336,14 +384,17 @@ The emitted skill body opens with a "Wiki Sources Baked Into This Skill" section
336
384
  - Client: each member card has Edit + Delete buttons + an iOS-style switch for activate/deactivate. Inactive members fade to 55% opacity. "+ Add member" button on the view header opens a generic edit modal with name / title / expertise (comma-separated) / persona / voiceGuide.
337
385
 
338
386
  **Principles CRUD**
387
+
339
388
  - Server: `POST /api/principles`, `PATCH /api/principles/:id`, `DELETE /api/principles/:id`. `coerceCategory()` validates against the `PrincipleCategory` enum.
340
389
  - Client: "+ Add principle" button. Edit form with title / description / behavior / category dropdown / priority. Click any card to edit. Inline switch for activate/deactivate.
341
390
 
342
391
  **Settings editing**
392
+
343
393
  - Server: `PATCH /api/settings` — merges with current settings, with type coercion for numeric fields that arrive as strings from the form.
344
394
  - Client: 12-field form with proper input types — text fields, number fields with min/max, dropdowns for orchestrator style + model aliases (incl. specific Claude IDs), iOS-style switches for booleans (`autoSummarization`, `enableUserInteraction`), help text under tricky fields.
345
395
 
346
396
  **Visual polish** (`gui/style.css`)
397
+
347
398
  - Bumped contrast tokens: `--text` `#e6e9ef` → `#f1f3f7`, `--text-dim` `#98a3b8` → `#b4bccc`, `--text-faint` `#6b7689` → `#818a9d`, borders darker by ~10%. The "dim disabled-look" in the user's first screenshot is gone.
348
399
  - New iOS-style `.switch` component with smooth slide animation.
349
400
  - New `.btn-danger` (filled red) and `.btn-danger-ghost` (outlined red) for destructive actions.
@@ -352,6 +403,7 @@ The emitted skill body opens with a "Wiki Sources Baked Into This Skill" section
352
403
  - View-header `gap: 16px` so action buttons (`+ Add member`) don't crowd the title.
353
404
 
354
405
  **Verified live (curl):**
406
+
355
407
  - `POST /api/members` → created Test Member, agent file `test-member.md` appeared in `.claude/agents/`
356
408
  - `PATCH /api/members/:id` `{isActive: false}` → updated correctly
357
409
  - `DELETE /api/members/:id` → returned 204, agent file was cleaned up
@@ -367,8 +419,11 @@ The emitted skill body opens with a "Wiki Sources Baked Into This Skill" section
367
419
  **Root cause:** `.modal-backdrop { display: flex }` overrode the `[hidden]` UA-stylesheet rule. The HTML `hidden` attribute corresponds to `display: none` via the `[hidden]` UA rule, which has the same CSS specificity (0,0,1,0) as a class selector. Cascade tie → author rule wins → modal visible. Was always broken; only became visible when I added a 2nd and 3rd `.modal-backdrop` element (`#edit-modal`, `#confirm-modal`).
368
420
 
369
421
  **Fix** (`gui/style.css` — one line at top of Modal block):
422
+
370
423
  ```css
371
- [hidden] { display: none !important; }
424
+ [hidden] {
425
+ display: none !important;
426
+ }
372
427
  ```
373
428
 
374
429
  Covers all `hidden` attribute usages, not just modals (also fixes a brief flash of the empty workspace card before bootstrap completed).
@@ -380,6 +435,7 @@ Covers all `hidden` attribute usages, not just modals (also fixes a brief flash
380
435
  **Trigger:** "I want to display the user's message on the discussion as well, like it was a message app. Also display in the 3 dots animation what's happening — searching the web etc. If possible stream the answer or at least display the answers as the board members are done and not all shown at the end."
381
436
 
382
437
  **(1) User messages as chat bubbles** (`gui/app.js`, `gui/style.css`)
438
+
383
439
  - New `userBubble(text, label, selectedOption?)` renderer — right-aligned, brand-gradient color, asymmetric corners (`14px 14px 4px 14px`), 👤 avatar.
384
440
  - New `discussionTimeline(discussion)` walker that interleaves user bubbles with member responses correctly:
385
441
  - Initial question (from `userResponses[type='initial_question']`) at the top
@@ -390,12 +446,14 @@ Covers all `hidden` attribute usages, not just modals (also fixes a brief flash
390
446
  - New `.message-user`, `.user-bubble`, `.avatar-user` styles.
391
447
 
392
448
  **(2) Per-member streaming response broadcast**
449
+
393
450
  - Engine extended `StartProgressEvent` union: `member_done` now carries `response: Response` and `roundNumber: number`. Added new `member_activity` and `orchestrator_decided` variants.
394
451
  - All three runMember call sites (`startDiscussion`, `continueDiscussion`, `addFollowUpQuestion`) pass the response/roundNumber on `member_done` and emit `orchestrator_decided` after the post-round orchestrator call.
395
- - Server: unified `broadcastRoundProgress` to broadcast `member_response` *immediately* on each `member_done` engine event (not in a post-hoc loop at end). `orchestrator_decided` → `orchestrator_decision` WS event mid-stream. Old "loop through every round at end and rebroadcast all responses" is gone.
452
+ - Server: unified `broadcastRoundProgress` to broadcast `member_response` _immediately_ on each `member_done` engine event (not in a post-hoc loop at end). `orchestrator_decided` → `orchestrator_decision` WS event mid-stream. Old "loop through every round at end and rebroadcast all responses" is gone.
396
453
  - The `POST /api/discussions` handler now uses the same unified broadcaster (with empty `discussionId` for the initial round — client matches typing bubbles by `memberName`, not by discussionId).
397
454
 
398
455
  **(3) Live activity in typing dots** (`src/llm/claude-code-runner.ts`, `src/core/discussion/run-member.ts`, `gui/app.js`, `gui/style.css`)
456
+
399
457
  - `runClaude` got new `onEvent?: (event: ClaudeStreamEvent) => void` + `streaming?: boolean` options. When set, switches to `--output-format stream-json --verbose` and parses stdout line-by-line via a new `onLine` callback in `spawnRaw`. Final `{type:"result"...}` line is still extracted into `result.json` so token-usage logging keeps working.
400
458
  - Added `parseLastResultLine()` helper.
401
459
  - `runMember` got new `onActivity?` option. Internally creates `makeActivityForwarder()` that maps Claude stream events to friendly strings:
@@ -412,12 +470,14 @@ Covers all `hidden` attribute usages, not just modals (also fixes a brief flash
412
470
  - Added small `cssEscape()` helper for safe attribute selector building.
413
471
 
414
472
  **(4) Race-condition fix: pre-create typing bubbles**
473
+
415
474
  - Bug surfaced after (2)+(3): user clicked Submit, saw user bubble + Round 1 divider but no typing bubbles. Server WS events fired correctly (verified), but the browser was still awaiting the POST response when `member_thinking` arrived → `addTypingBubble` ran with no `#chat-stream` in DOM yet → silent no-op.
416
- - `submitNewDiscussion` now opens the chat view *before* the fetch (synchronous DOM setup) and pre-creates a typing bubble for each selected member up front. The dedupe in `addTypingBubble` (`if (existing) return`) means subsequent server `member_thinking` events are no-ops once they arrive.
475
+ - `submitNewDiscussion` now opens the chat view _before_ the fetch (synchronous DOM setup) and pre-creates a typing bubble for each selected member up front. The dedupe in `addTypingBubble` (`if (existing) return`) means subsequent server `member_thinking` events are no-ops once they arrive.
417
476
  - On `discussion_gated` (pre-round gate fired, no members spawned), pending typing bubbles are cleaned up so they don't sit forever.
418
477
  - In `finalizeChat`, any typing bubble that never got a matching `member_response` (silent member failure) gets replaced with a `✗ No response` system bubble — useful safety net for genuine failures, but it became the symptom of the next bug.
419
478
 
420
479
  **Verified live (WS monitor):**
480
+
421
481
  ```
422
482
  [ws] member_thinking · Elon Musk
423
483
  [ws] member_activity · Elon Musk → searching the web… (Bitcoin price today May 2026)
@@ -433,14 +493,16 @@ Covers all `hidden` attribute usages, not just modals (also fixes a brief flash
433
493
 
434
494
  ### Bug: orphan typing bubbles after responses arrive
435
495
 
436
- **Trigger:** Screenshot showing pre-created typing bubbles still saying "writing response …" at the top of the stream while the actual responses appeared *below* them. Eventually `discussion_completed` fired and `finalizeChat` converted the orphans to "✗ No response — failed or timed out", which looked alarming.
496
+ **Trigger:** Screenshot showing pre-created typing bubbles still saying "writing response …" at the top of the stream while the actual responses appeared _below_ them. Eventually `discussion_completed` fired and `finalizeChat` converted the orphans to "✗ No response — failed or timed out", which looked alarming.
437
497
 
438
498
  **Root cause:** The WS `member_response` event had no top-level `memberName` field — only `msg.response.memberName`. But the client handler read:
499
+
439
500
  ```js
440
501
  } else if (msg.type === 'member_response') {
441
502
  replaceTypingWithResponse(msg.memberName, msg.response); // msg.memberName = undefined
442
503
  }
443
504
  ```
505
+
444
506
  `state.pendingTyping.get(undefined)` → undefined → else branch → `appendChild(responseBubble)` at the bottom. The pre-created typing bubble stayed orphaned.
445
507
 
446
508
  **This bug was always there.** It was invisible before today's pre-creation work because typing bubbles only existed for the brief window between `member_thinking` and the end-of-round response broadcast — and even then, the response usually didn't replace, it just got `appendChild`'d underneath. With pre-creation, the typing bubble lives for the whole round, making the orphan behavior obvious.
@@ -448,13 +510,16 @@ Covers all `hidden` attribute usages, not just modals (also fixes a brief flash
448
510
  **Fix — three layers, all additive (no regression):**
449
511
 
450
512
  1. **Client handler reads the right field, with fallback** (`gui/app.js`):
513
+
451
514
  ```js
452
515
  const name = msg.memberName || msg.response?.memberName;
453
516
  replaceTypingWithResponse(name, msg.response);
454
517
  ```
518
+
455
519
  Handles both the new (top-level) and old (nested) shape.
456
520
 
457
521
  2. **Server adds `memberName` + `memberId` at top-level of `member_response` event for symmetry** (`src/gui/server.ts`):
522
+
458
523
  ```js
459
524
  broadcast({
460
525
  type: 'member_response',
@@ -465,11 +530,13 @@ Covers all `hidden` attribute usages, not just modals (also fixes a brief flash
465
530
  ...
466
531
  });
467
532
  ```
533
+
468
534
  Future code that reads `msg.memberName` for any event type now Just Works.
469
535
 
470
536
  3. **`replaceTypingWithResponse` falls back to DOM search** (`gui/app.js`) — uses the existing `[data-typing-for="..."]` attribute on every typing bubble. If `state.pendingTyping` ever drifts out of sync (some future code path forgets to update it), the DOM is the source of truth and the bubble still gets replaced.
471
537
 
472
538
  **Verified live (WS monitor):**
539
+
473
540
  ```
474
541
  [member_response] msg.memberName= "Elon Musk" · msg.response.memberName= "Elon Musk"
475
542
  [member_response] msg.memberName= "Julian Bent Singh" · msg.response.memberName= "Julian Bent Singh"
package/dist/bin/aab.js CHANGED
@@ -3629,11 +3629,13 @@ ${brand()}
3629
3629
  process.stdout.write("\n" + c.bold("Next steps:") + "\n");
3630
3630
  process.stdout.write(` ${c.cyan("aab doctor")} ${c.hint("\u2014 verify everything is wired up")}
3631
3631
  `);
3632
- process.stdout.write(` ${c.cyan("aab settings get")}
3632
+ process.stdout.write(` ${c.cyan("aab ui")} ${c.hint("\u2014 open the local web dashboard at http://127.0.0.1:3737 (recommended)")}
3633
3633
  `);
3634
3634
  process.stdout.write(` ${c.cyan("aab discuss start")} ${c.hint('"What should we focus on this quarter?"')}
3635
3635
  `);
3636
3636
  process.stdout.write(` ${c.cyan("aab knowledge ingest")} ${c.hint("<path-or-url> \u2014 seed the wiki")}
3637
+ `);
3638
+ process.stdout.write(` ${c.cyan("aab settings get")}
3637
3639
  `);
3638
3640
  process.stdout.write("\n");
3639
3641
  }
@@ -5805,10 +5807,26 @@ async function ingestFile(opts) {
5805
5807
  throw new UserError(`ingest: not a file: ${opts.path}`);
5806
5808
  }
5807
5809
  const buf = readFileSync13(opts.path);
5810
+ return ingestFileBuffer({
5811
+ buffer: buf,
5812
+ originalName: basename3(opts.path),
5813
+ workspace: opts.workspace,
5814
+ settings: opts.settings,
5815
+ force: opts.force,
5816
+ hintType: opts.hintType,
5817
+ modelOverride: opts.modelOverride
5818
+ });
5819
+ }
5820
+ async function ingestFileBuffer(opts) {
5821
+ const buf = opts.buffer;
5822
+ if (!buf || buf.length === 0) {
5823
+ throw new UserError("ingest: file is empty.");
5824
+ }
5825
+ const leaf = basename3(opts.originalName.replace(/\\/g, "/")) || "upload";
5808
5826
  const hash = sha256Hex(buf);
5809
5827
  const h6 = hash.slice(0, 6);
5810
- const ext = extname(opts.path).toLowerCase() || ".md";
5811
- const sanitizedName = sanitizeFilename(basename3(opts.path, extname(opts.path)));
5828
+ const ext = extname(leaf).toLowerCase() || ".md";
5829
+ const sanitizedName = sanitizeFilename(basename3(leaf, extname(leaf)));
5812
5830
  const rawFilename = `${h6}-${sanitizedName}${ext}`;
5813
5831
  const p = paths(opts.workspace.root);
5814
5832
  ensureWikiDirs(opts.workspace.root);
@@ -5819,7 +5837,7 @@ async function ingestFile(opts) {
5819
5837
  }
5820
5838
  const rawRelPath = toPosix(relative2(opts.workspace.root, rawPath));
5821
5839
  let inlineBody;
5822
- if ([".md", ".txt", ".json", ".csv", ".yaml", ".yml"].includes(ext)) {
5840
+ if ([".md", ".markdown", ".txt", ".json", ".csv", ".tsv", ".yaml", ".yml"].includes(ext)) {
5823
5841
  try {
5824
5842
  inlineBody = buf.toString("utf8");
5825
5843
  } catch {
@@ -5833,7 +5851,7 @@ async function ingestFile(opts) {
5833
5851
  rawRelPath,
5834
5852
  sourceType: "file",
5835
5853
  hash,
5836
- originalName: basename3(opts.path),
5854
+ originalName: opts.originalName,
5837
5855
  hintType: opts.hintType,
5838
5856
  inlineBody,
5839
5857
  force: opts.force,
@@ -11971,7 +11989,12 @@ async function startUiServer(opts) {
11971
11989
  const projectRoot = opts.projectRoot ?? process.cwd();
11972
11990
  const guiDir = resolveGuiDir();
11973
11991
  const app = express();
11974
- app.use(express.json({ limit: "256kb" }));
11992
+ const jsonStd = express.json({ limit: "256kb" });
11993
+ const jsonLarge = express.json({ limit: "64mb" });
11994
+ app.use((req, res, next) => {
11995
+ if (req.method === "POST" && req.path === "/api/knowledge/ingest") return jsonLarge(req, res, next);
11996
+ return jsonStd(req, res, next);
11997
+ });
11975
11998
  const sockets = /* @__PURE__ */ new Set();
11976
11999
  const broadcast = (evt) => {
11977
12000
  const data = JSON.stringify(evt);
@@ -13212,16 +13235,27 @@ async function startUiServer(opts) {
13212
13235
  const body = req.body ?? {};
13213
13236
  const workspace = resolveWorkspaceForWiki();
13214
13237
  const settings = await opts.storage.loadSettings();
13215
- broadcast({ type: "wiki_ingest_started", sourceType: body.url ? "url" : body.path ? "file" : "pasted" });
13238
+ const sourceType = body.url ? "url" : body.path || body.file ? "file" : "pasted";
13239
+ broadcast({ type: "wiki_ingest_started", sourceType });
13216
13240
  let result;
13217
13241
  if (body.paste) {
13218
13242
  result = await ingestPaste({ text: body.paste, workspace, settings, force: body.force, hintType: body.type });
13219
13243
  } else if (body.url) {
13220
13244
  result = await ingestUrl({ url: body.url, workspace, settings, force: body.force, hintType: body.type });
13245
+ } else if (body.file?.contentBase64) {
13246
+ const buffer = Buffer.from(body.file.contentBase64, "base64");
13247
+ result = await ingestFileBuffer({
13248
+ buffer,
13249
+ originalName: body.file.name?.trim() || "upload",
13250
+ workspace,
13251
+ settings,
13252
+ force: body.force,
13253
+ hintType: body.type
13254
+ });
13221
13255
  } else if (body.path) {
13222
13256
  result = await ingestFile({ path: body.path, workspace, settings, force: body.force, hintType: body.type });
13223
13257
  } else {
13224
- res.status(400).json({ error: "Provide paste, url, or path." });
13258
+ res.status(400).json({ error: "Provide paste, url, file, or path." });
13225
13259
  return;
13226
13260
  }
13227
13261
  for (const page of result.producedPages) broadcast({ type: "wiki_ingest_page_written", path: page, action: "created" });