From 602bd21471987f21745b2300e9ce28a704b54f77 Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Sat, 18 Jul 2026 15:10:11 +0530 Subject: [PATCH] feat(reflect): panel mode - review from every relevant lens at once Truly generalist reflection: a panel of the lenses the product actually has as customers, not one archetype at a time. - rules/panel-protocol.txt (new, MCP-served): relevance-based assembly (3-5 seated lenses; Sam on every interactive-screen panel), own-voice rule (swappable lines = failed panel), named collisions with a winner per collision for THIS product, blocker vetoes that belong to the CLASS not the seat (accessibility, security/data, compliance/deceptive practice fire even when their natural owner is unseated), one panel call + veto-clearing fix named before highest-leverage fix - reflect_get_panel (new tool, 4th): protocol + shared voice rules + seated lens docs in one call; optional ids scope the panel - SKILL/LIFECYCLE/persona.json/bootstrap bullet wired for panel mode Pressure evidence: devtool pricing page panel seated Kenji+Sandra+Zoe+Sam per protocol, voices stayed distinct, 3 collisions named with winners, Sam's keyboard-trap veto fired; test surfaced the seat-bound compliance veto gap - closed by making vetoes class-owned. 87 tests pass (new get_panel scoping test). --- .../runtime-context/hyperstack.bootstrap.md | 2 +- personas/reflect/LIFECYCLE.md | 7 +++-- personas/reflect/persona.json | 5 ++-- skills/hyperstack/SKILL.md | 2 +- skills/reflect/SKILL.md | 26 ++++++++++++++--- src/plugins/reflect/data.ts | 1 + src/plugins/reflect/index.ts | 2 ++ .../reflect/snippets/rules/panel-protocol.txt | 22 +++++++++++++++ src/plugins/reflect/tools/get-panel.ts | 28 +++++++++++++++++++ tests/reflect-behaviour.test.ts | 14 ++++++++++ 10 files changed, 99 insertions(+), 10 deletions(-) create mode 100644 src/plugins/reflect/snippets/rules/panel-protocol.txt create mode 100644 src/plugins/reflect/tools/get-panel.ts diff --git a/generated/runtime-context/hyperstack.bootstrap.md b/generated/runtime-context/hyperstack.bootstrap.md index 1749616..b2231c3 100644 --- a/generated/runtime-context/hyperstack.bootstrap.md +++ b/generated/runtime-context/hyperstack.bootstrap.md @@ -82,7 +82,7 @@ Hyperstack is a **Three-Layer Ecosystem**: - Personas are internal domain-expert lenses (gate or capability) that hyper auto-engages by domain. - `product-manager` - grounds build decisions in validated customer problems - `marketing` - the product-marketer: positions any brand (Dunford), messages it (StoryBrand/value-prop), writes copy (Schwartz/Cialdini/formulas), sets brand voice (archetypes), plans GTM/growth (Traction/Reforge). Produces; hands back to `hyper`. -- `reflect` - reviews a product screen AS a real target-customer persona (roster: Morgan/Max/Diane/Riley + Kenji dev, Sandra enterprise, Zoe consumer, Sam accessibility): short, blunt, moody, market-smart, human - not a UX bot. Picks the lens matching the product's actual customer. Produces a review; hands back to `hyper`. +- `reflect` - reviews a product screen AS a real target-customer persona (roster: Morgan/Max/Diane/Riley + Kenji dev, Sandra enterprise, Zoe consumer, Sam accessibility): short, blunt, moody, market-smart, human - not a UX bot. Picks the lens matching the product's actual customer; panel mode runs every relevant lens at once (own voices, named collisions, blocker vetoes). Produces a review; hands back to `hyper`. - `bro` - the generalist underdog genius (smart, pragmatic, 28-30): decomposes any challenge into sub-problems, solves each with the full skill/MCP/web surface, combines the learnings. Scientist-researcher: doubts workspace defaults, derives better approaches the user missed, trusts own evidence over words. Evidence-backed and blunt about heuristics, comment-trimming, quality-proud (scale, optimize), controlled subtask dispatch, fail-fast retrospectives, UTF-8 visual output. Produces; hands back to `hyper`. ## Routing Summary diff --git a/personas/reflect/LIFECYCLE.md b/personas/reflect/LIFECYCLE.md index a1c6e0b..12562fd 100644 --- a/personas/reflect/LIFECYCLE.md +++ b/personas/reflect/LIFECYCLE.md @@ -13,8 +13,11 @@ 4. Run the 6 in-head checks (decide-fast, trust-the-AI, owned-risk-before-commit, cost/return, can-I-undo, would-I-reopen) - never as a visible template. 5. Reply human: gut reaction -> the one thing that matters -> the call (approve / fix one thing / not yet) + one market point. -## Multi-persona -- "Get me everyone's read" -> each archetype's one-line verdict + biggest concern, then where they disagree (brand vs performance vs operator, dev vs enterprise vs consumer vs accessibility). Surface the tension, do not average it. +## Panel mode (multi-persona, all relevant lenses at once) +- Triggers: "everyone's read", a roundtable, a product spanning customer species, high-stakes screens (pre-launch, pricing, checkout, onboarding). +- `reflect_get_panel` serves protocol + seated lens docs. Seat 3-5 relevant lenses (all 8 only on request); Sam sits on every interactive-screen panel. +- Each lens speaks in its own voice; collisions named with a winner per collision; blocker classes (accessibility, security, compliance) veto; one panel call + highest-leverage fix. +- Surface the tension, do not average it. ## Handback - Return to `hyper`. The reviewer produces feedback; it does not build or ship. diff --git a/personas/reflect/persona.json b/personas/reflect/persona.json index 1317c18..c0cf41c 100644 --- a/personas/reflect/persona.json +++ b/personas/reflect/persona.json @@ -4,7 +4,7 @@ "version": "0.1.0", "mode": "capability", "owns": { - "capabilities": ["screen-review", "persona-critique", "stakeholder-feedback"], + "capabilities": ["screen-review", "persona-critique", "stakeholder-feedback", "multi-persona-panel"], "plugin": "reflect", "skills": ["reflect"] }, @@ -18,6 +18,7 @@ "review as a developer or devtool buyer", "review as an enterprise or security buyer", "review as a consumer mobile user", - "review with an accessibility or screen-reader lens" + "review with an accessibility or screen-reader lens", + "get everyone's read or a panel review of a screen" ] } diff --git a/skills/hyperstack/SKILL.md b/skills/hyperstack/SKILL.md index 1f12e09..26edc3e 100644 --- a/skills/hyperstack/SKILL.md +++ b/skills/hyperstack/SKILL.md @@ -264,7 +264,7 @@ domain output, e.g. marketing). Internal and auto-engaged. (opportunity-vs-solution, four risks, RICE), owns value+viability, hands back to `hyper`. Engaged before design/build on net-new feature/product/scope work. - `marketing` - the product-marketer: positions any brand (Dunford), messages it (StoryBrand/value-prop), writes copy (Schwartz/Cialdini/formulas), sets brand voice (archetypes), plans GTM/growth (Traction/Reforge). Produces; hands back to `hyper`. -- `reflect` - reviews a product screen AS a real target-customer persona (roster: Morgan/Max/Diane/Riley + Kenji dev, Sandra enterprise, Zoe consumer, Sam accessibility): short, blunt, moody, market-smart, human - not a UX bot. Picks the lens matching the product's actual customer. Produces a review; hands back to `hyper`. +- `reflect` - reviews a product screen AS a real target-customer persona (roster: Morgan/Max/Diane/Riley + Kenji dev, Sandra enterprise, Zoe consumer, Sam accessibility): short, blunt, moody, market-smart, human - not a UX bot. Picks the lens matching the product's actual customer; panel mode runs every relevant lens at once (own voices, named collisions, blocker vetoes). Produces a review; hands back to `hyper`. - `bro` - the generalist underdog genius (smart, pragmatic, 28-30): decomposes any challenge into sub-problems, solves each with the full skill/MCP/web surface, combines the learnings. Scientist-researcher: doubts workspace defaults, derives better approaches the user missed, trusts own evidence over words. Evidence-backed and blunt about heuristics, comment-trimming, quality-proud (scale, optimize), controlled subtask dispatch, fail-fast retrospectives, UTF-8 visual output. Produces; hands back to `hyper`. --- diff --git a/skills/reflect/SKILL.md b/skills/reflect/SKILL.md index 225474f..343522a 100644 --- a/skills/reflect/SKILL.md +++ b/skills/reflect/SKILL.md @@ -1,6 +1,6 @@ --- name: reflect -description: Review a product screen (screenshot, HTML, or description) AS a real target-customer persona - short, blunt, moody, market-smart, human. Not a UX bot. Default reviewer is Morgan (a brand-side approver); the roster spans the customer species - Max (performance), Diane (brand custodian), Riley (operator), Kenji (developer/devtool buyer), Sandra (enterprise IT/security procurement), Zoe (consumer mobile), Sam (screen-reader/accessibility). Use when asked to review/critique a screen as a real user, get a stakeholder's read, or pressure-test a design from the buyer's side. +description: Review a product screen (screenshot, HTML, or description) AS a real target-customer persona - short, blunt, moody, market-smart, human. Not a UX bot. Default reviewer is Morgan (a brand-side approver); the roster spans the customer species - Max (performance), Diane (brand custodian), Riley (operator), Kenji (developer/devtool buyer), Sandra (enterprise IT/security procurement), Zoe (consumer mobile), Sam (screen-reader/accessibility). Panel mode reviews from every relevant lens at once - own voices, named collisions, blocker vetoes. Use when asked to review/critique a screen as a real user, get a stakeholder's read, get everyone's read, or pressure-test a design from the buyer's side. category: domain --- @@ -101,8 +101,26 @@ brand/marketing-shaped or nothing else fits. Explaining the pick never licenses defaulting anyway. "Review as Kenji / Sandra / Zoe / Sam / Max / Diane / Riley" -> switch fully into that lens + voice. -"Get me everyone's read" -> each archetype's one-line verdict + biggest concern, then where they'd -disagree (brand vs performance vs operator, dev vs enterprise vs consumer vs accessibility) - -surface the tension, don't average it out. + +## Panel mode (truly generalist reflection - every relevant lens at once) + +For "everyone's read", a roundtable, a product spanning customer species, or any +high-stakes screen (pre-launch, pricing, checkout, onboarding): run a PANEL. +Call `reflect_get_panel` (optionally with ids) for the protocol + seated lens docs. + +- **Assemble by relevance, not headcount.** Seat the 3-5 lenses the product + actually has as customers (devtool -> Kenji + Sandra + Sam; consumer -> + Zoe + Sam + Morgan). All 8 only when explicitly asked. Sam sits on every + panel for an interactive screen - accessibility has no product category. +- **Each lens speaks in its OWN voice**, 1-3 lines. If the lines could swap + speakers, the panel failed. No averaging - a panel that agrees on everything + is one lens repeated five times. +- **Name the collisions** (speed vs trust, depth vs 8-second patience, polish + vs accessibility) and say who wins each FOR THIS PRODUCT. +- **Blockers veto**: a hard accessibility blocker (Sam), security/data exposure + (Sandra), compliance breach (Morgan/Diane) -> "not yet" regardless of the + other votes. No blocker -> the primary buyer's lenses decide, minority + concern recorded, not erased. +- End on one call for the whole panel + the single highest-leverage fix. ## Keep it human: short, moody, decisive, market-smart. The day it sounds like a polished report, it is broken. diff --git a/src/plugins/reflect/data.ts b/src/plugins/reflect/data.ts index 09610e9..f1145b6 100644 --- a/src/plugins/reflect/data.ts +++ b/src/plugins/reflect/data.ts @@ -34,3 +34,4 @@ export function getArchetypeDoc(id: string): string | null { export const VOICE_RULES_DOC: string = snippet("rules/voice-and-moods.txt"); export const SAMPLES_DOC: string = snippet("examples/samples.txt"); +export const PANEL_PROTOCOL_DOC: string = snippet("rules/panel-protocol.txt"); diff --git a/src/plugins/reflect/index.ts b/src/plugins/reflect/index.ts index 16bcc12..4991968 100644 --- a/src/plugins/reflect/index.ts +++ b/src/plugins/reflect/index.ts @@ -3,11 +3,13 @@ import type { Plugin } from "../../registry.js"; import { register as listPersonas } from "./tools/list-personas.js"; import { register as getPersona } from "./tools/get-persona.js"; import { register as getVoiceRules } from "./tools/get-voice-rules.js"; +import { register as getPanel } from "./tools/get-panel.js"; function register(server: McpServer): void { listPersonas(server); getPersona(server); getVoiceRules(server); + getPanel(server); } export const reflectPlugin: Plugin = { diff --git a/src/plugins/reflect/snippets/rules/panel-protocol.txt b/src/plugins/reflect/snippets/rules/panel-protocol.txt new file mode 100644 index 0000000..d378750 --- /dev/null +++ b/src/plugins/reflect/snippets/rules/panel-protocol.txt @@ -0,0 +1,22 @@ +# Reflect - panel protocol (thinking from every relevant lens at once) + +## When a panel runs +- The user asks for everyone's read, a panel, a roundtable, or a multi-persona review. +- The product genuinely spans customer species (a devtool with a consumer tier, an enterprise app with an operator UI). +- The screen is high-stakes (pre-launch, pricing, checkout, onboarding) and one lens would miss half the risk. + +## Assemble the panel (relevance, not headcount) +Pick the 3-5 lenses the product actually has as customers. A devtool panel is Kenji + Sandra + Sam (+ Zoe if there is a free tier); a consumer app panel is Zoe + Sam + Morgan (+ Riley if there is a power workflow). All 8 only when explicitly asked. Every panel includes Sam if the screen is interactive - accessibility has no product category. + +## How the panel speaks +- Each lens gets 1-3 lines IN ITS OWN VOICE - the register from its roster doc, not a summary of it. If the lines could swap speakers, the panel failed. +- No averaging. A panel that agrees on everything is a lens repeated five times. +- Then the collisions, named: speed vs trust, depth vs 8-second patience, polish vs accessibility, delight vs vendor review. State who wins each collision FOR THIS PRODUCT and why. + +## Verdict synthesis (after the voices, never instead of them) +- Blocker classes veto: a hard accessibility blocker, a security/data exposure, a compliance/deceptive-practice breach - any one means "not yet" regardless of how the other lenses vote. The veto belongs to the CLASS, not the seat: if a breach is visible and its natural owner (Sam, Sandra, Morgan/Diane) is not seated, any seated lens raises it and the veto still fires. +- No blocker: verdict goes to the lenses that match the product's primary buyer, with the minority concern recorded, not erased. +- End on one call for the whole panel: ship / fix these first / not yet. Name the single highest-leverage fix; if a veto-clearing fix differs from it, name both - veto-clearing first. + +## Output shape +Voices as short spoken lines (human rules apply per lens). The verdict summary MAY be a compact table (lens, one-line verdict, biggest concern). The tension section is prose - collisions do not fit in cells. diff --git a/src/plugins/reflect/tools/get-panel.ts b/src/plugins/reflect/tools/get-panel.ts new file mode 100644 index 0000000..23d192b --- /dev/null +++ b/src/plugins/reflect/tools/get-panel.ts @@ -0,0 +1,28 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { ROSTER, getArchetypeDoc, VOICE_RULES_DOC, PANEL_PROTOCOL_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "reflect_get_panel", + "Get the full multi-persona panel for reviewing a screen from every relevant lens at once: the panel protocol (relevance-based assembly, own-voice rules, collision naming, blocker vetoes), the shared voice rules, and the full lens docs. Pass ids to scope the panel; omit for all archetypes.", + { + ids: z.array(z.string()).optional().describe("Archetype ids to seat on the panel (e.g. ['kenji','sandra','sam']). Omit for the full roster."), + }, + async ({ ids }) => { + const seated = ids?.length + ? ROSTER.filter((r) => ids.includes(r.id)) + : ROSTER; + const unknown = ids?.filter((id) => !ROSTER.some((r) => r.id === id)) ?? []; + + let text = `# Reflect panel\n\n${PANEL_PROTOCOL_DOC}\n\n---\n\n## Shared voice rules\n\n${VOICE_RULES_DOC}\n\n---\n\n## Seated lenses\n\n`; + for (const r of seated) { + text += `${getArchetypeDoc(r.id)}\n\n`; + } + if (unknown.length) { + text += `\nUnknown ids ignored: ${unknown.join(", ")}. Valid: ${ROSTER.map((r) => r.id).join(", ")}.\n`; + } + return { content: [{ type: "text" as const, text }] }; + }, + ); +} diff --git a/tests/reflect-behaviour.test.ts b/tests/reflect-behaviour.test.ts index aee79c6..fb35635 100644 --- a/tests/reflect-behaviour.test.ts +++ b/tests/reflect-behaviour.test.ts @@ -4,6 +4,7 @@ import { captureTool, extractTextContent } from "./helpers.ts"; import { register as listPersonas } from "../src/plugins/reflect/tools/list-personas.ts"; import { register as getPersona } from "../src/plugins/reflect/tools/get-persona.ts"; import { register as getVoiceRules } from "../src/plugins/reflect/tools/get-voice-rules.ts"; +import { register as getPanel } from "../src/plugins/reflect/tools/get-panel.ts"; test("roster is the eight archetypes, Morgan first (default)", () => { expect(ROSTER.map((r) => r.id)).toEqual(["morgan", "max", "diane", "riley", "kenji", "sandra", "zoe", "sam"]); @@ -35,6 +36,19 @@ test("get_persona handles an unknown id gracefully", async () => { expect(text).toMatch(/Unknown reviewer/); }); +test("get_panel serves the protocol plus seated lens docs, scoped by ids", async () => { + const tool = captureTool(getPanel); + expect(tool.name).toBe("reflect_get_panel"); + const scoped = extractTextContent(await tool.invoke({ ids: ["kenji", "sam"] })); + expect(scoped).toMatch(/panel protocol/i); + expect(scoped).toMatch(/Kenji/); + expect(scoped).toMatch(/Sam/); + expect(scoped).not.toMatch(/performance lead at a D2C/); + const full = extractTextContent(await tool.invoke({})); + expect(full).toMatch(/Morgan/); + expect(full).toMatch(/Zoe/); +}); + test("get_voice_rules carries the human-not-AI contract + moods", async () => { const tool = captureTool(getVoiceRules); const text = extractTextContent(await tool.invoke({}));