From 7ac399c0d85798a37cd770c35888f26091695d39 Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Sun, 21 Jun 2026 21:01:53 +0530 Subject: [PATCH 01/16] docs(persona): PM persona vertical design spec + research corpus Adds a 4th 'personas/' vertical and a product-manager persona that owns the value+viability product risks Hyperstack currently leaves unowned. Tiered gate (hard for net-new, advisory for tweaks, user override). Spec grounded in adversarial deep research (24 claims confirmed, primary sources: Cagan/SVPG, Torres, Intercom, Christensen, Doshi); corpus persisted alongside. Phase 1 = scaffold+plugin+skill+wiring; gaps stubbed NEEDS-RESEARCH. --- docs/research/2026-06-21-pm-craft-corpus.json | 349 ++++++++++++++++++ .../specs/2026-06-21-pm-persona-design.md | 194 ++++++++++ 2 files changed, 543 insertions(+) create mode 100644 docs/research/2026-06-21-pm-craft-corpus.json create mode 100644 docs/superpowers/specs/2026-06-21-pm-persona-design.md diff --git a/docs/research/2026-06-21-pm-craft-corpus.json b/docs/research/2026-06-21-pm-craft-corpus.json new file mode 100644 index 0000000..96b0143 --- /dev/null +++ b/docs/research/2026-06-21-pm-craft-corpus.json @@ -0,0 +1,349 @@ +{ + "summary": "Deep research harness — fan-out web searches, fetch sources, adversarially verify claims, synthesize a cited report.", + "agentCount": 103, + "logs": [ + "Q: How do strong product managers make high-quality product decisions, and what sep…", + "Decomposed into 5 angles: Pillar 1 - Customer/problem grounding & discovery, Pillar 2 - Prioritization frameworks & scope, Pillar 3 - Decision-making under uncertainty & conviction, Failure modes & anti-patterns of weak PMs, Mental models, product sense & frameworks of the best PMs", + "Pillar 1 - Customer/problem grounding & discovery: 6 results", + "Pillar 2 - Prioritization frameworks & scope: 6 results", + "Pillar 3 - Decision-making under uncertainty & conviction: 6 results", + "Failure modes & anti-patterns of weak PMs: 6 results", + "Mental models, product sense & frameworks of the best PMs: 6 results", + "Pillar 3 - Decision-making under uncertainty & conviction: 3 novel (3 filtered)", + "Failure modes & anti-patterns of weak PMs: 4 novel (2 filtered)", + "Mental models, product sense & frameworks of the best PMs: 2 novel (4 filtered)", + "Fetched 21 sources → 94 claims → verifying top 25", + "\"An opportunity solution tree is a visual framework…\": 3-0 ✓", + "\"Visualizing the discovery/opportunity space preven…\": 3-0 ✓", + "\"A concrete test distinguishes a real opportunity f…\": 3-0 ✓", + "\"Cagan defines exactly four product risks that stro…\": 3-0 ✓", + "\"Responsibility for the four risks is split across …\": 3-0 ✓", + "\"Continuous discovery requires engaging with custom…\": 3-0 ✓", + "\"PMs must not ask customers directly what they want…\": 3-0 ✓", + "\"Cagan argues weak product managers systematically …\": 3-0 ✓", + "\"Product discovery is fundamentally a decision-maki…\": 3-0 ✓", + "\"The RICE prioritization score is computed as (Reac…\": 3-0 ✓", + "\"RICE stands for four distinct scoring factors - Re…\": 3-0 ✓", + "\"RICE was created to counteract specific prioritiza…\": 3-0 ✓", + "\"Prioritization is fundamentally an act of saying n…\": 3-0 ✓", + "\"Product strategy (not the roadmap) is the mechanis…\": 3-0 ✓", + "\"Strong product companies decide what to build by p…\": 3-0 ✓", + "\"A defining anti-pattern of weak (feature-team) pro…\": 3-0 ✓", + "\"A JTBD 'job' is defined as the progress a person w…\": 3-0 ✓", + "\"Marty Cagan's core teaching method for product man…\": 3-0 ✓", + "\"The source provides an explicit, falsifiable check…\": 3-0 ✓", + "\"Jobs to Be Done reframes product decisions around …\": 3-0 ✓", + "\"Direct customer feedback questions like 'how can w…\": 3-0 ✓", + "\"Christensen's Four Forces of Progress is a codifia…\": 3-0 ✓", + "\"JTBD distinguishes a real 'job' from an assumption…\": 1-2 ✗", + "\"The LNO framework is a task-prioritization and ant…\": 3-0 ✓", + "\"Shreyas Doshi argues that most execution problems …\": 3-0 ✓", + "Verify done: 25 claims → 24 confirmed, 1 killed" + ], + "result": { + "question": "How do strong product managers make high-quality product decisions, and what separates good PMs from bad ones? Focus on three pillars: (1) Customer/problem grounding - product discovery, jobs-to-be-done, customer behaviour analysis, user research, distinguishing real needs from assumptions, validation before building; (2) Prioritization and scope - frameworks (RICE, opportunity sizing, Kano, MoSCoW, ICE), MVP definition, scope-cutting, saying no, sequencing/roadmapping; (3) Decision-making and conviction - making opinionated defensible calls under uncertainty, structuring decisions with clear rationale and evidence, product strategy, trade-off reasoning, avoiding generic/hedged decisions. Also cover: the mental models and frameworks the best PMs use, common failure modes and anti-patterns of weak PMs, and how 'product sense' is actually developed. Prioritize authoritative sources (Marty Cagan / SVPG / INSPIRED / EMPOWERED, Lenny Rachitsky, Teresa Torres / continuous discovery, Shreyas Doshi, John Cutler, Melissa Perri, Reforge, Intercom, a16z, classic PM texts). The output will become the ground-truth corpus for a 'product manager' persona/enforcement layer in an AI coding agent system, so emphasize concrete, codifiable frameworks, decision rules, checklists, and red-flag anti-patterns over generic advice.", + "summary": "Strong product managers make high-quality decisions by grounding every build choice in validated customer problems, ruthlessly prioritizing a focused set of problems over a long feature list, and making opinionated, evidence-backed calls that explicitly address the four product risks (value, usability, feasibility, viability). The corpus converges on a coherent, codifiable system: discovery is continuous evidence-gathering (weekly customer touchpoints, story-based interviews that never ask customers what they want), prioritization is fundamentally an act of saying no to good ideas mapped against a desired outcome (opportunity solution trees, RICE), and decision-making is principles-first rather than technique-copying. The single sharpest line between good and bad PMs is reactivity vs. vision-driven focus: weak PMs over-index on customer value while avoiding business viability, react to the loudest sales call or support ticket, and ship as many stakeholder features as possible (which Cagan labels \"not a strategy at all\"). Product sense is developed by mastering enduring principles so the PM can judge when each technique applies, and by diagnosing root causes (most \"execution problems\" are really strategy, interpersonal, or culture problems) rather than applying band-aids. The strongest, most enforceable rules in this corpus come from primary sources - Marty Cagan/SVPG, Teresa Torres/Product Talk, and Intercom (RICE's originator) - and are unanimous and current as of 2026.", + "findings": [ + { + "claim": "Customer/problem grounding is continuous and evidence-based, not project-based: good discovery teams engage customers AT LEAST WEEKLY so few build decisions are made without customer input, and discovery is fundamentally a decision-making process that includes the customer throughout (not just at the start). Codifiable cadence rule for the persona: enforce a weekly customer-contact touchpoint and flag any build decision lacking customer evidence.", + "confidence": "high", + "sources": [ + "https://www.producttalk.org/2021/08/product-discovery/" + ], + "evidence": "Primary source (Teresa Torres, originator of continuous discovery). Verbatim: 'Good product discovery teams engage with customers at least weekly, minimizing the number of decisions they make without customer input' and 'Product discovery is a decision-making process. Good product discovery includes the customer throughout that process.' Corroborated by IxDF, Product School, Maze, Mind the Product, Headway. Nuance: 'weekly' is an aspirational best-practice benchmark, not an absolute no-exceptions rule.", + "vote": "3-0 (claims 6, 8)" + }, + { + "claim": "PMs must NOT ask customers directly what they want or need - cognitive biases make opinion-based answers unreliable. Instead, elicit needs, pain points, and desires through specific past-behavior customer STORIES. This is the core story-based-interview rule for distinguishing real needs from assumptions, and a directly enforceable red flag: any requirement justified by 'a customer said they want X' (opinion/hypothetical) rather than an observed story is suspect.", + "confidence": "high", + "sources": [ + "https://www.producttalk.org/2021/08/product-discovery/", + "https://www.fullstory.com/blog/clayton-christensen-jobs-to-be-done-framework-product-development/" + ], + "evidence": "Primary (Torres) verbatim: 'We can't simply ask customers what they need or want. Cognitive biases interfere with our customers ability to answer these questions reliably' followed by 'listen for needs, pain points, and desires in the context of specific stories.' Independently reinforced by Rob Fitzpatrick's 'The Mom Test' (avoid opinions/hypotheticals) and the JTBD milkshake case: direct feedback ('how can we improve?') surfaced superficial taste/feature requests, but the real job was occupying a long boring commute - 'they had a long and boring drive to work and they just needed something to do.' Demographics fail to predict intent.", + "vote": "3-0 (claims 7, 21)" + }, + { + "claim": "The Opportunity Solution Tree is the canonical map from business value to validated solutions: a four-layer hierarchy of (1) desired business OUTCOME at the root, (2) OPPORTUNITY SPACE (customer needs/pains/desires), (3) SOLUTION SPACE, (4) ASSUMPTION TESTS at the bottom. Visualizing the full opportunity space counteracts reactive, anecdote-driven decisions where teams overweight the most recent interview, sales call, or support ticket - letting PMs compare and contrast opportunities instead.", + "confidence": "high", + "sources": [ + "https://www.producttalk.org/opportunity-solution-trees/" + ], + "evidence": "Primary source (Teresa Torres, framework originator). All four layers confirmed verbatim: root = 'desired outcome - the business need that reflects how your team can create business value'; opportunity space = 'customer needs, pain points, and desires'; solution space = 'solutions we are exploring'; assumption tests = 'how we will evaluate which solutions.' Anti-recency mechanism verbatim: 'It is common for product teams to overreact to the most recent customer interview, sales conversation, or support ticket... we are better able to compare and contrast the value of addressing different opportunities.' Corroborated by Shortform, Product School, Amplitude, Maze, Chameleon.", + "vote": "3-0 (claims 0, 1)" + }, + { + "claim": "A concrete, codifiable test separates a real opportunity from a solution disguised as one: ask 'Is there more than one way to address this?' If only ONE way exists, it is a solution, not an opportunity. This is a directly enforceable red-flag check against premature solution-jumping (e.g. 'I want to go out to eat' is a solution; the underlying 'I don't have time to cook' is the opportunity, satisfiable by takeout, meal-prep, restaurants, etc.).", + "confidence": "high", + "sources": [ + "https://www.producttalk.org/opportunity-solution-trees/" + ], + "evidence": "Primary source verbatim: 'The best way to test if an opportunity is really a solution in disguise is to ask, Is there more than one way to address this opportunity?' Illustrated with the cook/eat example. Corroborated by multiple summaries of Torres's 'Continuous Discovery Habits.' A clean yes/no rule fit for an enforcement layer.", + "vote": "3-0 (claim 2)" + }, + { + "claim": "Strong teams must address EXACTLY FOUR product risks before building: VALUE (will customers buy / users choose to use it), USABILITY (can users figure out how to use it), FEASIBILITY (can engineers build it with available time/skills/tech), and BUSINESS VIABILITY (does it work for the various parts of the business - legal, sales, finance, marketing, etc.). Cagan deliberately resists a fifth to keep it 'a way of thinking, not a checklist.' This is the master decision-quality checklist for evaluating any product idea.", + "confidence": "high", + "sources": [ + "https://www.svpg.com/four-big-risks/" + ], + "evidence": "Primary (Marty Cagan / SVPG, INSPIRED 2nd ed.) verbatim enumerates all four with definitions matching the claim near word-for-word. The 'exactly four' framing defended in Cagan's 2023 'Product Risk Taxonomy': 'once you get to more than 4, the real worry is that it just becomes a checklist and not a way of thinking.' Corroborated by roadmap.one, Viget, Medium. Foundational and current.", + "vote": "3-0 (claim 3)" + }, + { + "claim": "Risk ownership is split across the product trio by role: the Product Manager owns VALUE and VIABILITY risk and is accountable for outcomes; the Product Designer owns USABILITY risk and is accountable for the experience; the Lead Engineer owns FEASIBILITY risk and is accountable for delivery. For a PM persona, this scopes exactly what the PM is on the hook for: is it valuable and is it viable for the business.", + "confidence": "high", + "sources": [ + "https://www.svpg.com/four-big-risks/" + ], + "evidence": "Primary (SVPG) verbatim: 'The Product Manager is responsible for the value and viability risks, and overall accountable for the products outcomes. The Product Designer is responsible for the usability risk... The Product Lead Engineer is responsible for the feasibility risk.' Corroborated by Cagan's 'Empowered Product Teams' doctrine. 'Trio' is community shorthand for the three-role structure.", + "vote": "3-0 (claim 4)" + }, + { + "claim": "A NAMED FAILURE MODE of weak PMs: they systematically over-focus on customer value and under-appreciate, under-estimate, or simply AVOID business viability risk. The four-risks taxonomy exists specifically to correct this gap. Enforcement implication: when a PM's reasoning covers desirability/value but is silent on whether the business can actually sell, support, fund, or legally ship it, flag the missing viability analysis.", + "confidence": "high", + "sources": [ + "https://www.svpg.com/four-big-risks/" + ], + "evidence": "Primary (Cagan) verbatim: 'The business value (viability) risks can be substantial and I find that these are too often under-appreciated and under-estimated (or simply avoided) by the product manager,' and the old model was 'making it too easy for product managers to focus on customer value and overlook business value.' The taxonomy is explicitly framed as the corrective. Minor nuance: Cagan attributes this to 'the product manager' generically (driven by the old mental model) rather than literally labeling them 'weak.'", + "vote": "3-0 (claim 5)" + }, + { + "claim": "RICE is the canonical comparative prioritization score, computed as (Reach x Impact x Confidence) / Effort, yielding 'total impact per time worked' to rank competing initiatives. The four factors (Reach, Impact, Confidence, Effort) are each estimated SEPARATELY before being combined into one comparable number. RICE was explicitly created to counteract two prioritization failure modes: personal bias toward favorite/pet ideas, and inconsistent, non-comparable decisions across diverse projects.", + "confidence": "high", + "sources": [ + "https://www.intercom.com/blog/rice-simple-prioritization-for-product-managers/" + ], + "evidence": "Primary source = Intercom, the ORIGINATOR of RICE (built by Sean McBride). Verbatim: 'RICE Score Formula: (Reach x Impact x Confidence) / Effort. The resulting score measures total impact per time worked.' Failure modes verbatim: 'satisfying to work on pet ideas you would use yourself, instead of projects with broad reach' and the difficulty 'consistently combining and comparing these factors.' Corroborated by ProductPlan, Product School, Tempo, Ducalis, Whatfix. Caveat: critics (Dovetail, Medium) note inputs remain gameable (effort sandbagging, confidence inflation) - RICE structures consistency rather than mechanically enforcing it.", + "vote": "3-0 (claims 9, 10, 11)" + }, + { + "claim": "Product STRATEGY (not the roadmap) is the mechanism that decides WHICH problems to solve now, and it works by focusing on a FEW critical business levers - because most companies dilute their efforts by doing too many things at once. Prioritization is fundamentally an act of SAYING NO: focus means rejecting the hundred other good ideas, not just selecting the chosen ones. Enforcement: a 'strategy' that is a long list of everything is a non-strategy; force concentration on 2-3 levers and explicit rejection of the rest.", + "confidence": "high", + "sources": [ + "https://www.svpg.com/changing-how-you-decide-which-problems-to-solve/" + ], + "evidence": "Primary (SVPG / Cagan + Jon Moore) verbatim: 'the product strategy is how we identify the most important problems to solve now... begins by focusing on the most critical areas for business success. Most companies try to do too many things at once, and end up diluting their efforts... on the true levers for the business.' Saying-no principle anchored to authenticated Steve Jobs WWDC 1997 quote: 'focus... means saying no to the hundred other good ideas' (corroborated Farnam Street, CNBC, WWDC transcript). Cagan separately opposes feature-roadmaps, giving teams 'a prioritised set of business problems to solve.' Note: primary URL returned HTTP 403; quotes confirmed via multiple independent renderings.", + "vote": "3-0 (claims 12, 13)" + }, + { + "claim": "The defining anti-pattern of weak/reactive product organizations: they REACT to sales opportunities, competitors, customer requests, and price pressure instead of pursuing a customer-centric product vision; and in most feature-team companies the de facto 'strategy' is to ship as many stakeholder features as possible - 'which is to say, there really isn't a product strategy at all.' These are the highest-value codifiable red flags for distinguishing strong from weak PM behavior.", + "confidence": "high", + "sources": [ + "https://www.svpg.com/changing-how-you-decide-which-problems-to-solve/" + ], + "evidence": "Primary (SVPG) verbatim: 'So many companies spend their time reacting - reacting to new sales opportunities, reacting to competitors offerings, reacting to customer requests, and reacting to price pressure... in strong product companies, while they care about these factors, they are not driven by them. What drives them is the pursuit of a product vision.' And: 'in most feature-team companies, the product strategy is literally to try to deliver as many features as possible for the different stakeholders. Which is to say, there really isn't a product strategy at all' (verified verbatim via Jina reader + independent search). Consistent across INSPIRED/EMPOWERED/TRANSFORMED. Minor counter-nuance: some commentators argue Cagan's feature-team definition is overstated and stakeholders are partners/constraints, not to be dismissed.", + "vote": "3-0 (claims 14, 15)" + }, + { + "claim": "Product sense / PM craft is developed PRINCIPLES-FIRST, not techniques-first. A solid grasp of enduring principles is what gives a PM the mental model to judge WHEN a given technique applies and when it does not, and to evaluate new techniques as they emerge. Techniques churn constantly; principles endure. This is presented as the central differentiator between strong PMs and those who merely copy techniques - directly relevant to building a persona that reasons from fundamentals rather than pattern-matching trendy frameworks.", + "confidence": "high", + "sources": [ + "https://www.svpg.com/principles/" + ], + "evidence": "Primary (Cagan / SVPG) verbatim: 'when a person reaches the point that they have a solid understanding of the principles, they develop a good mental model for when each technique is useful and appropriate, and when it is not. Further, as new techniques emerge, they are able to quickly assess the potential value of the technique.' Also 'while the techniques change fairly constantly, the underlying principles endure.' Caveat: the explicit 'strong vs weak PM' contrast is the claimant's faithful gloss, not literal essay text. Note: canonical URL returned 403; verified via AgileAus mirror + multiple searches.", + "vote": "3-0 (claim 16)" + }, + { + "claim": "Jobs-To-Be-Done reframes product decisions around the JOB a customer 'hires' a product to accomplish (intent/causation) rather than around demographics, which fail to predict purchasing intent. A JTBD 'job' is the PROGRESS a person wants to make under specific circumstances - a process, not a single action - and includes FUNCTIONAL, EMOTIONAL, and SOCIAL dimensions. The operative distinction from a generic feature request is that a job captures progress + context across all three dimensions, not a discrete asked-for feature.", + "confidence": "high", + "sources": [ + "https://gopractice.io/product/jobs-to-be-done-the-theory-and-the-frameworks/", + "https://www.fullstory.com/blog/clayton-christensen-jobs-to-be-done-framework-product-development/" + ], + "evidence": "gopractice (secondary) verbatim: 'A job is the progress a person wants to achieve under specific circumstances. It isn't a single action but a process.' Backed by PRIMARY sources: Christensen Institute ('the progress they're trying to make... functional, social, and emotional dimensions') and 'Competing Against Luck' (2016). 'Hire' framing + demographics critique verbatim in FullStory and Christensen's HBR 2005 'Marketing Malpractice': demographics give correlation not causation - 'Demographics fail to predict intent.' Minor nuance: the Ulwick ODI school treats emotional/social as separate jobs rather than dimensions of one; claim states the mainstream Christensen lineage.", + "vote": "3-0 (claims 17, 20)" + }, + { + "claim": "Christensen's Four Forces of Progress is a codifiable switching predictor: two PUSH forces drive change (F1 frustration with the status quo, F2 attraction to the new product) and two RESISTANCE forces oppose it (F3 anxiety of learning the new, F4 habit/comfort of the current solution). A switch happens ONLY when Push + Pull > Anxiety + Habit. Useful for the persona as a model for predicting adoption and for diagnosing why a 'better' product still fails to win switchers.", + "confidence": "high", + "sources": [ + "https://gopractice.io/product/jobs-to-be-done-the-theory-and-the-frameworks/" + ], + "evidence": "gopractice (secondary) lists F1 push / F2 pull / F3 anxiety / F4 habit. Switching rule corroborated verbatim by learningloop.io and koji.so: 'When Push + Pull > Anxiety + Habit, the switch happens'; also jobstobedone.org (Moesta) and Christensen Institute. Primary grounding: 'Competing Against Luck' (2016). Two minor imprecisions, neither falsifying: the diagram was originated by Bob Moesta & Chris Spiek (~2012) and popularized by Christensen (so 'Christensen's' is loose co-attribution); 'new product' vs canonical 'new solution' is trivial paraphrase.", + "vote": "3-0 (claim 18)" + }, + { + "claim": "There is an explicit, falsifiable CHECKLIST for whether a job statement is valid: it must NOT be a trend, NOT a high-level need, NOT specific to one product class, must NOT imply a single solution, MUST include both the progress sought and the context (external + emotional), and must sit at the appropriate abstraction level. Directly codifiable as a validator gate for any 'job' or problem statement the persona accepts or emits.", + "confidence": "high", + "sources": [ + "https://gopractice.io/product/jobs-to-be-done-the-theory-and-the-frameworks/" + ], + "evidence": "Verified via direct WebFetch: source contains a 'Checklist for defining a job' with 7 numbered items mapping onto every claim condition (isn't a trend / isn't high-level / isn't one product class / doesn't imply a single solution / includes task+progress AND context external+emotional / appropriate abstraction). Each is a testable yes/no criterion ('falsifiable' is fair). Broader JTBD literature (Christensen Institute, Ulwick/Strategyn, Productboard) corroborates that valid jobs are solution-agnostic, tech-independent, appropriately scoped, with functional+emotional dimensions. Secondary-source quality is sufficient since the claim only asserts the source provides such a checklist.", + "vote": "3-0 (claim 19)" + }, + { + "claim": "Diagnostic decision rule (Shreyas Doshi): most 'execution problems' are NOT really execution problems - when a team appears to be failing at execution, the root cause is usually a deeper problem being band-aided. IMPORTANT CORPUS CORRECTION: Doshi names THREE roots, not one - STRATEGY, INTERPERSONAL, and CULTURE problems. Good leaders diagnose and fix the root; bad leaders apply band-aids. The persona should state this as 'strategy OR interpersonal OR culture,' not strategy/prioritization alone.", + "confidence": "high", + "sources": [ + "https://www.lennysnewsletter.com/p/episode-3-shreyas-doshi" + ], + "evidence": "Primary (Doshi's own tweet, Oct 2020): 'Most Execution problems are really 1) Strategy problems, or 2) Interpersonal problems, or 3) Culture problems. Good leaders execute well because they understand this. They fix the root problem. Bad leaders struggle because they are always applying band-aids.' Lenny's Podcast topic verbatim: 'Most execution problems are not really execution problems.' SCOPE DEFECT in original claim: it narrowed root cause to strategy/prioritization, dropping interpersonal and culture - corrected here. Core diagnostic (apparent execution failure masks a deeper root) is accurate and primary-sourced.", + "vote": "3-0 (claim 22)" + }, + { + "claim": "The LNO framework (Shreyas Doshi) is a task-prioritization and anti-procrastination system that classifies work by return on effort into LEVERAGE (10-100x return - invest heavily, pursue excellence/perfectionism), NEUTRAL (~1.1x return - do a solid 'good enough' job), and OVERHEAD (necessary but low return - deliberately do a 'bad'/minimal job). PMs should invest DISPROPORTIONATE effort in high-leverage work rather than treating all tasks equally. Codifiable as an effort-allocation rule: match perfectionism to leverage tier.", + "confidence": "high", + "sources": [ + "https://www.lennysnewsletter.com/p/episode-3-shreyas-doshi" + ], + "evidence": "Primary (Doshi's Coda doc, coda.io/@shreyas/lno-framework): categorizes work by return on effort - Leverage '10x or even 100x return', Neutral '1.0 effort results in 1.1x return', Overhead 'necessary but provide little return' (advice: actively try to do a bad job). Original 2021 X thread: 'all your tasks are not created equal. There are 3 types of PM tasks: 1) Leverage 2) Neutral 3) Overhead.' Anti-procrastination element confirmed in the Coda source (addresses perfectionism-driven avoidance of hard L tasks). Still actively cited 2024-2026.", + "vote": "3-0 (claim 23)" + } + ], + "caveats": "SOURCE QUALITY: 18 of 24 confirmed claims rest on PRIMARY sources from the exact authorities the brief prioritized - Marty Cagan/SVPG (4 risks, ownership, viability failure mode, strategy/saying-no, reactive anti-patterns, principles-first), Teresa Torres/Product Talk (continuous discovery, story-based interviews, opportunity solution trees), and Intercom (RICE's originator). These are the strongest, most enforceable rules in the corpus and should carry the most weight in the persona/enforcement layer. The JTBD cluster (5 claims) is anchored to a SECONDARY aggregator (gopractice.io) but is backed throughout by primary Christensen sources (Christensen Institute, HBR 2005, 'Competing Against Luck' 2016), so confidence remains high. The two Shreyas Doshi claims cite a SECONDARY podcast page but are corroborated by Doshi's own primary tweets/Coda docs.\n\nONE MATERIAL CORRECTION (do not propagate the original wording): Claim 22 as originally stated narrowed the root cause of execution problems to 'strategy/prioritization.' Doshi's actual primary source names THREE roots - strategy, interpersonal, AND culture. The persona must use the three-way version; the strategy-only framing is a scope error.\n\nACCESS NOTE: several SVPG URLs (four-big-risks, changing-how-you-decide, principles) returned HTTP 403 to direct fetches; their verbatim quotes were confirmed via independent mirrors (AgileAus), reader proxies (Jina), and multiple WebSearch renderings that agreed word-for-word. The quotes are reliable, but no single first-party page load backs them.\n\nFRAMING NUANCES (faithful but not literal): 'weekly customer contact' is an aspirational best-practice benchmark, not an absolute no-exceptions law (Torres allows context-dependent exceptions). 'Weak PMs' labels in the Cagan claims are interpretive glosses - Cagan attributes the viability-avoidance tendency to 'the product manager' generically, driven by an outdated mental model, not to a named 'weak PM' category. RICE 'enforces' consistency is slightly strong: inputs (effort, confidence) remain gameable, so RICE structures/encourages comparability rather than mechanically guaranteeing it. The Four Forces diagram is co-attributable to Bob Moesta & Chris Spiek, not Christensen alone.\n\nCOVERAGE GAPS: the confirmed corpus is thin on Pillar 2 frameworks beyond RICE and saying-no - Kano, MoSCoW, ICE, opportunity sizing, and explicit MVP/scope-cutting definitions did not survive into confirmed claims, so the persona's prioritization toolkit is currently RICE + opportunity solution trees + Cagan strategy, not the full named set in the brief. Reforge, a16z, John Cutler, and Melissa Perri are not represented among confirmed sources. TIME-SENSITIVITY: all frameworks here are stable, foundational, and still canonical as of 2026; none are at risk of going stale, but the field's tooling references (e.g., AI-assisted discovery) evolve and were not part of this corpus.", + "openQuestions": [ + "How do the best PMs actually DEFINE and CUT an MVP / minimum scope in practice? The confirmed corpus establishes 'saying no' as a principle and RICE for ranking, but contains no codifiable rule for where to draw the MVP line, how to sequence a roadmap, or how to decide what is the smallest valuable slice - a gap for the enforcement layer.", + "What are the concrete mechanics of the prioritization frameworks named in the brief but absent from confirmed claims - Kano (delighters vs must-haves), MoSCoW, ICE, and opportunity sizing? Only RICE survived verification; the persona currently lacks rules for the others.", + "How is 'product sense' developed beyond the principles-first stance - i.e., what are the repeatable PRACTICES (pattern libraries, deliberate teardowns, mentorship, feedback loops) that the best operators (Doshi, Lenny Rachitsky, Julie Zhuo) prescribe for actually building judgment over time?", + "How should a PM structure and document a defensible DECISION under uncertainty (the explicit rationale/evidence template, pre-mortems, decision logs, disagree-and-commit)? The corpus strongly establishes WHY conviction matters and which anti-patterns to avoid, but offers no concrete decision-writeup format to enforce." + ], + "refuted": [ + { + "claim": "JTBD distinguishes a real 'job' from an assumption/generic need via context: needs are 'fairly universal and don't take into account the person's context,' whereas jobs incorporate the user's specific situation, making context the test for whether a stated need is real.", + "vote": "1-2", + "source": "https://gopractice.io/product/jobs-to-be-done-the-theory-and-the-frameworks/" + } + ], + "sources": [ + { + "url": "https://www.producttalk.org/opportunity-solution-trees/", + "quality": "primary", + "angle": "Pillar 1 - Customer/problem grounding & discovery", + "claimCount": 5 + }, + { + "url": "https://www.svpg.com/four-big-risks/", + "quality": "primary", + "angle": "Pillar 1 - Customer/problem grounding & discovery", + "claimCount": 5 + }, + { + "url": "https://gopractice.io/product/jobs-to-be-done-the-theory-and-the-frameworks/", + "quality": "secondary", + "angle": "Pillar 1 - Customer/problem grounding & discovery", + "claimCount": 5 + }, + { + "url": "https://www.producttalk.org/2021/08/product-discovery/", + "quality": "primary", + "angle": "Pillar 1 - Customer/problem grounding & discovery", + "claimCount": 5 + }, + { + "url": "https://www.fullstory.com/blog/clayton-christensen-jobs-to-be-done-framework-product-development/", + "quality": "secondary", + "angle": "Pillar 1 - Customer/problem grounding & discovery", + "claimCount": 5 + }, + { + "url": "https://blog.logrocket.com/product-management/opportunity-solution-trees-definition-examples-how-to/", + "quality": "blog", + "angle": "Pillar 1 - Customer/problem grounding & discovery", + "claimCount": 5 + }, + { + "url": "https://www.intercom.com/blog/rice-simple-prioritization-for-product-managers/", + "quality": "primary", + "angle": "Pillar 2 - Prioritization frameworks & scope", + "claimCount": 5 + }, + { + "url": "https://www.lennysnewsletter.com/p/episode-3-shreyas-doshi", + "quality": "secondary", + "angle": "Pillar 2 - Prioritization frameworks & scope", + "claimCount": 5 + }, + { + "url": "https://www.svpg.com/changing-how-you-decide-which-problems-to-solve/", + "quality": "primary", + "angle": "Pillar 2 - Prioritization frameworks & scope", + "claimCount": 5 + }, + { + "url": "https://www.productlift.dev/blog/product-prioritization-framework-comparison/", + "quality": "blog", + "angle": "Pillar 2 - Prioritization frameworks & scope", + "claimCount": 5 + }, + { + "url": "https://www.atlassian.com/agile/product-management/prioritization-framework", + "quality": "blog", + "angle": "Pillar 2 - Prioritization frameworks & scope", + "claimCount": 5 + }, + { + "url": "https://www.productplan.com/glossary/rice-scoring-model", + "quality": "secondary", + "angle": "Pillar 2 - Prioritization frameworks & scope", + "claimCount": 5 + }, + { + "url": "https://www.lennyspodcast.com/blog/shreyas-doshi-on-pre-mortems-lno-framework-3-levels-of-product-work-and-prioritization/", + "quality": "unreliable", + "angle": "Pillar 3 - Decision-making under uncertainty & conviction", + "claimCount": 0 + }, + { + "url": "https://blackboxofpm.com/making-good-decisions-as-a-product-manager-c66ddacc9e2b", + "quality": "unreliable", + "angle": "Pillar 3 - Decision-making under uncertainty & conviction", + "claimCount": 0 + }, + { + "url": "https://pmctraining.com/articles-and-resources/decision-quality-vs-outcome-quality/", + "quality": "secondary", + "angle": "Pillar 3 - Decision-making under uncertainty & conviction", + "claimCount": 5 + }, + { + "url": "https://cutlefish.substack.com/p/tbm-852-top-1-product-managers", + "quality": "blog", + "angle": "Failure modes & anti-patterns of weak PMs", + "claimCount": 5 + }, + { + "url": "https://itamargilad.com/pm-antipatterns/", + "quality": "blog", + "angle": "Failure modes & anti-patterns of weak PMs", + "claimCount": 5 + }, + { + "url": "https://www.prodpad.com/blog/agile-anti-patterns/", + "quality": "blog", + "angle": "Failure modes & anti-patterns of weak PMs", + "claimCount": 5 + }, + { + "url": "https://userpilot.com/blog/feature-factory/", + "quality": "blog", + "angle": "Failure modes & anti-patterns of weak PMs", + "claimCount": 5 + }, + { + "url": "https://www.lennysnewsletter.com/p/product-sense", + "quality": "blog", + "angle": "Mental models, product sense & frameworks of the best PMs", + "claimCount": 5 + }, + { + "url": "https://www.svpg.com/principles/", + "quality": "primary", + "angle": "Mental models, product sense & frameworks of the best PMs", + "claimCount": 4 + } + ], + "stats": { + "angles": 5, + "sourcesFetched": 21, + "claimsExtracted": 94, + "claimsVerified": 25, + "confirmed": 24, + "killed": 1, + "afterSynthesis": 16, + "urlDupes": 3, + "budgetDropped": 6, + "agentCalls": 103 + } + } +} \ No newline at end of file diff --git a/docs/superpowers/specs/2026-06-21-pm-persona-design.md b/docs/superpowers/specs/2026-06-21-pm-persona-design.md new file mode 100644 index 0000000..91f8281 --- /dev/null +++ b/docs/superpowers/specs/2026-06-21-pm-persona-design.md @@ -0,0 +1,194 @@ +# Design: Product-Manager Persona + `personas/` Vertical + +Hyperstack enforces *how to work* (Iron Laws, gates) and *how it looks/builds* (designer, engineering-discipline), but nothing owns *what to build and why*. This spec adds a fourth architectural vertical - `personas/` - and its first inhabitant, a `product-manager` persona that grounds build decisions in validated customer problems, prioritizes ruthlessly, and makes opinionated, evidence-backed calls. It bites as a tiered gate before design/build. The persona's rules are not invented: they are 11 framework rules confirmed by adversarial deep research against primary sources (Cagan/SVPG, Torres/Product Talk, Intercom, Christensen, Doshi), persisted at `docs/research/2026-06-21-pm-craft-corpus.json`. + +## Problem statement + +| Symptom (user-reported) | Root cause | Evidence | +|---|---|---| +| "Decisions are random gibberish" | No layer owns product VALUE - what to build is assumed, not validated | Four-Risks taxonomy: PM owns value+viability; Hyperstack has neither | +| "Not real PM-level calls" | No customer-behaviour grounding before work starts | Discovery is continuous + story-based; Hyperstack jumps to how-work | +| "Enforcement leaks" (decision quality, not mechanics) | Output hedges / sprawls; no prioritized, defensible call | "Strategy = saying no"; weak PMs ship everything | + +The four product risks map onto Hyperstack's roles and expose the exact hole: + +``` +RISK OWNER STATUS +────────────────────────────────────────────────────── +VALUE → Product Manager → MISSING ◄ the leak +VIABILITY → Product Manager → MISSING ◄ the leak +USABILITY → Designer (designer skill) → exists +FEASIBILITY → Engineer (eng-discipline) → exists +``` + +The PM persona completes the trio. It is not a bolt-on; it fills a structurally missing accountability. + +## Goals / Non-goals + +| Goals | Non-goals | +|---|---| +| Add `personas/` as a first-class 4th vertical, extensible to future personas | Not a parallel MCP transport - tools register through the existing `loadPlugins` path | +| Ship `product-manager` persona = bound MCP plugin + skill + agent role + manifest | Not project-specific product opinions - corpus is generic, source-cited PM craft (keeps core generic) | +| Tiered enforcement: hard-gate net-new, advisory tweaks, user override always | Not a replacement for designer/engineering-discipline - it precedes them | +| Ground every tool in a verified research claim; stub unproven areas as NEEDS-RESEARCH | Not faking the 4 research-gap areas (MVP-cut, Kano/MoSCoW/ICE, decision-template) | + +## Research foundation (corpus → tools) + +Every PM tool is backed by a confirmed claim. Full provenance in the persisted corpus. + +| Rule | Tool surface | Source (confidence) | +|---|---|---| +| Four Product Risks (value/usability/feasibility/viability); PM owns value+viability | `get_four_risks` | Cagan/SVPG (high) | +| Opportunity-vs-solution test: "more than one way to address this?" | `opportunity_vs_solution` | Torres (high) | +| JTBD job = progress-in-context (functional/emotional/social); Four Forces | `get_jtbd` | Christensen (high) | +| Job-statement validator (7-point falsifiable checklist) | `validate_job_statement` | gopractice/Christensen (high) | +| Story-based discovery; never ask "what do you want"; weekly contact | `get_discovery_rules` | Torres (high) | +| RICE = (Reach x Impact x Confidence) / Effort | `score_rice` | Intercom, RICE originator (high) | +| Strategy = saying no; focus 2-3 levers; long list = non-strategy | `get_strategy_rules` | Cagan/SVPG (high) | +| Anti-patterns: feature-factory, reactivity, viability-avoidance, execution-misdiagnosis (strategy/interpersonal/culture) | `get_anti_patterns` | Cagan + Doshi (high) | +| Orchestrator: description → grounded product decision + verdict | `resolve_product_decision` | composition of above | + +Killed claim (do not encode): "context is THE test for a real job" (1-2 refuted). Correction encoded: execution problems trace to strategy OR interpersonal OR culture (three roots, not one). + +## Architecture: the `personas/` vertical + +``` +hyperstack/ +├── src/plugins/ Layer 1 MCP ground-truth (data) +├── skills/ Layer 2 process / discipline +├── agents/ Layer 3 routing roles (who executes) +└── personas/ Layer 4 ◄ NEW: judgment lenses that OWN decisions + ├── README.md vertical overview + ├── persona-registry.ts loads persona.json manifests; exposes to compiler + router + ├── persona.schema.json manifest schema + └── product-manager/ + ├── persona.json manifest (see below) + ├── PROFILE.md identity, mission, owns value+viability, voice + ├── LIFECYCLE.md engage criteria, gate steps, handback to hyper + ├── CHECKS.md the falsifiable gate checklist + ├── CONTEXT.md context slice it loads + └── corpus/ framework snippets (distilled from research JSON) +``` + +### Persona manifest (`persona.json`) + +```json +{ + "id": "product-manager", + "name": "Product Manager", + "version": "0.1.0", + "owns": { + "risks": ["value", "viability"], + "plugin": "product-manager", + "skills": ["pm-gate"], + "agent": "product-manager" + }, + "engages_when": ["net-new feature", "new product", "build request", "scope decision"], + "gate_policy": { "net_new": "hard", "tweak": "advisory", "override": "user-explicit" } +} +``` + +### Bound components + +| Part | Location | Contract | +|---|---|---| +| MCP plugin `product-manager` | `src/plugins/product-manager/` (existing plugin pattern: `index.ts`/`loader.ts`/`data.ts`/`tools/*`/`snippets/*`) | 9 tools above; stateless; logically persona-owned via manifest | +| Skill `pm-gate` | `skills/pm-gate/SKILL.md` | the product-decision workflow + tiered gate; Iron Law: "NO NET-NEW BUILD WITHOUT A PASSED PRODUCT DECISION" | +| Agent role `product-manager` | bound via manifest; contracts under `personas/product-manager/` | owns value+viability; engaged by hyper; hands back to hyper | +| Binding | `personas/product-manager/persona.json` | ties plugin + skill + agent + corpus + voice | + +Pragmatic concession to the 4th-vertical choice: tool *code* lives in `src/plugins/` so it registers through the only verified MCP path (`src/index.ts` → `loadPlugins`). The persona *owns* it via manifest. Physical relocation under `personas/` is possible but requires a new loader wired into `src/index.ts`; deferred unless required. + +Unverified mechanism (resolve in the plan, do not assume here): how the `product-manager` agent becomes a discoverable agent-type - declared in the plugin manifest, auto-discovered from an `agents/` dir, or realized purely through the persona contracts + `pm-gate` skill - was not confirmed during codebase analysis. The plan must verify Claude Code's agent-registration path before committing to one. + +## Lifecycle - where the gate bites + +``` +user request + │ + ▼ +hyper ── classify ──► build / feature / product call? + │ │ + │ YES │ engage product-manager persona [GATE] + │ ▼ + │ ┌─────────────────────────────────────┐ + │ │ 1 GROUND opportunity_vs_solution │ reject solutions-as-problems + │ │ discovery evidence? │ flag "customer said they want X" + │ │ 2 ASSESS get_four_risks │ esp VALUE + VIABILITY + │ │ 3 PRIORITIZE score_rice / saying-no │ what is the ONE call + │ │ 4 EMIT resolve_product_decision│ PASS | BLOCK | NEEDS-INPUT + rationale + │ └─────────────────────────────────────┘ + │ │ PASS + └── NO (tweak/bug/refactor) ──► advisory brief (no block) + ▼ + hyper routes → designer → builder → ship-gate +``` + +### Tiered enforcement policy + +| Request class | Gate | Behaviour | +|---|---|---| +| Net-new feature / product / scope decision | HARD | must emit PASS before designer/build; BLOCK halts with reason | +| Tweak / bugfix / refactor / non-product | ADVISORY | brief emitted, no block | +| User explicit "skip PM" | OVERRIDE | always honoured, logged in the decision trail | + +## Build integration + +| File | Change | Risk | +|---|---|---| +| `skills/hyperstack/SKILL.md` | add `## Personas` section (bootstrap source of truth) | LOW | +| `src/internal/context-compiler.ts` | extract + emit Personas layer; extend `REQUIRED_BOOTSTRAP_MARKERS` | MED - marker-validated, covered by `context-compiler-behaviour.test.ts` | +| `generated/runtime-context/hyperstack.bootstrap.md` | regenerated via `bun run compile:context` | auto | +| `harness/router.md`, `harness/transitions.md` | add persona-gate step `hyper → persona-gate → specialist`; allowed/disallowed transitions | LOW (prose) | +| `personas/persona-registry.ts`, `persona.schema.json` | new, small | LOW | +| `personas/product-manager/**` | manifest + contracts + corpus | LOW | +| `src/plugins/product-manager/**` | new plugin, mirrors `designer` exactly | LOW | +| `src/index.ts` | import + register `productManagerPlugin` | LOW | +| `tests/` | persona-registry, pm-gate verdict, bootstrap-marker tests | LOW (pattern exists) | + +## Data flow + +``` +research JSON ──(distill, build-time/manual)──► src/plugins/product-manager/snippets/*.txt + │ createSnippetLoader("product-manager") +tool call ──► handler ──► data.ts (typed) + snippet(txt) ──► markdown decision/checklist +pm-gate skill ──► calls product_manager_* tools ──► applies gate_policy ──► PASS|BLOCK|NEEDS-INPUT +persona-registry ──► reads persona.json ──► feeds compiler (bootstrap) + router (engage_when) +``` + +## Error handling / degraded mode + +| Failure | Behaviour | +|---|---| +| MCP unavailable | pm-gate states "MCP unavailable", falls back to corpus text, flags decision as ungrounded (consistent with existing degraded-mode rule) | +| Persona manifest malformed | persona-registry skips it with a surfaced warning (not silent), like plugin-registry resilience | +| Tool references missing snippet | build-time corpus-integrity check (added) fails loud, not at runtime | +| Gate verdict ambiguous | default to NEEDS-INPUT (ask user), never silent PASS | + +## Testing + +Mirror existing `*-behaviour.test.ts`: +- persona-registry loads `product-manager` manifest; malformed manifest is skipped with warning. +- bootstrap compiles with Personas markers present (extends marker test). +- `pm-gate` returns BLOCK when four-risks viability is unaddressed; PASS when all four covered + prioritized call present; NEEDS-INPUT on ambiguity. +- `opportunity_vs_solution` returns is_solution for a single-path statement. +- `validate_job_statement` fails a statement missing context. +- override path bypasses gate and logs. + +## Phasing + +| Phase | Scope | +|---|---| +| **1 (this plan)** | `personas/` scaffold + registry + schema; `product-manager` plugin (9 verified tools); `pm-gate` skill (tiered); bootstrap + router wiring; tests. Gap areas ship as explicit NEEDS-RESEARCH stubs. | +| **2 (later)** | targeted second research pass for the 4 gaps (MVP-cut mechanics, Kano/MoSCoW/ICE, product-sense practices, decision-writeup template); add decision-log template tool. | + +## Open items (research-flagged gaps, not faked) + +1. MVP / minimum-scope cut rule - no codifiable line yet. +2. Kano / MoSCoW / ICE / opportunity-sizing mechanics - only RICE survived verification. +3. Repeatable product-sense-building practices. +4. Defensible decision-writeup template (pre-mortem / decision-log format). + +## Future personas (why the vertical, not a one-off) + +The vertical is justified only if more personas follow. Candidates that fit the same "judgment lens that owns a risk" shape: a security persona (owns risk/threat), a data/analytics persona (owns measurement), an SRE persona (owns reliability). Each = manifest + bound plugin/skill/agent. The registry, schema, and bootstrap layer built here are shared infrastructure. From 3a2afbab0f937ba75b4e49b1c269ed9e0241d611 Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Sun, 21 Jun 2026 21:09:25 +0530 Subject: [PATCH 02/16] docs(persona): PM persona vertical implementation plan 3-part TDD plan (plugin / vertical infra / skill+binding), code-complete, grounded in the optimizer plugin pattern + verified test breakpoints (plugin count 13->14, bootstrap compile:context regen). --- .../plans/2026-06-21-pm-persona-vertical.md | 1175 +++++++++++++++++ 1 file changed, 1175 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-21-pm-persona-vertical.md diff --git a/docs/superpowers/plans/2026-06-21-pm-persona-vertical.md b/docs/superpowers/plans/2026-06-21-pm-persona-vertical.md new file mode 100644 index 0000000..2e64733 --- /dev/null +++ b/docs/superpowers/plans/2026-06-21-pm-persona-vertical.md @@ -0,0 +1,1175 @@ +# PM Persona Vertical Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a `personas/` 4th vertical and a `product-manager` persona that owns the value+viability product risks via a tiered decision gate, grounded in verified PM-craft research. + +**Architecture:** Three parts. **A** - a `product-manager` MCP plugin (pure `data.ts` + tools, mirrors the `optimizer` plugin: a framework menu, not implementations). **B** - the `personas/` vertical infrastructure (manifest schema + registry + a `Layer 4: Personas` section compiled into the bootstrap). **C** - the `pm-gate` skill + persona manifest + router wiring that bind plugin+skill+role into the tiered gate. Parts are review checkpoints; A is standalone, B is standalone (fixture-tested), C composes A+B. + +**Tech Stack:** TypeScript (ESM, `.js` import specifiers), `@modelcontextprotocol/sdk`, `zod`, `bun:test`. No new dependencies. + +**Branch:** `f-PE-pm-persona-vertical` (already created; spec + corpus already committed there). + +**Source of truth for corpus content:** `docs/research/2026-06-21-pm-craft-corpus.json` (committed). All framework text below is transcribed from confirmed claims in that file. + +--- + +## File Structure + +``` +PART A - plugin (testable, standalone) + Create src/plugins/product-manager/data.ts types + framework data + resolver fns + Create src/plugins/product-manager/index.ts Plugin export, registers 9 tools + Create src/plugins/product-manager/tools/*.ts 9 tool files (one register() each) + Modify src/index.ts import + add to allPlugins + Modify tests/plugin-registry-behaviour.test.ts 13 -> 14; assert PM tools present + Create tests/product-manager-behaviour.test.ts tool output assertions + +PART B - personas vertical (testable, fixture) + Create personas/persona.schema.json manifest JSON Schema + Create personas/persona-registry.ts load + validate manifests + Create personas/README.md vertical overview + Modify skills/hyperstack/SKILL.md + '## Layer 4: Personas' + '## Persona Registry' + Modify src/internal/context-compiler.ts extract/emit Personas + markers + Regen generated/runtime-context/hyperstack.bootstrap.md via compile:context + Create tests/persona-registry-behaviour.test.ts registry loads PM manifest + +PART C - skill + binding (composes A+B) + Create personas/product-manager/persona.json manifest binding plugin+skill+role + Create personas/product-manager/PROFILE.md identity, owns value+viability, voice + Create personas/product-manager/LIFECYCLE.md engage criteria, gate steps, handback + Create personas/product-manager/CHECKS.md the falsifiable gate checklist + Create skills/pm-gate/SKILL.md tiered gate workflow + Modify harness/router.md, harness/transitions.md persona-gate routing +``` + +--- + +# PART A - `product-manager` MCP plugin + +### Task A1: Plugin data layer (types + frameworks + resolvers) + +**Files:** +- Create: `src/plugins/product-manager/data.ts` +- Test: `tests/product-manager-behaviour.test.ts` + +- [ ] **Step 1: Write the failing test** + +Create `tests/product-manager-behaviour.test.ts`: + +```ts +import { test, expect } from "bun:test"; +import { + FOUR_RISKS, + classifyOpportunityVsSolution, + validateJobStatement, + scoreRice, + PM_OWNED_RISKS, +} from "../src/plugins/product-manager/data.ts"; + +test("four risks are exactly the canonical four", () => { + expect(FOUR_RISKS.map((r) => r.id).sort()).toEqual( + ["feasibility", "usability", "value", "viability"], + ); +}); + +test("PM owns exactly value and viability", () => { + expect([...PM_OWNED_RISKS].sort()).toEqual(["value", "viability"]); +}); + +test("single-path statement is classified as a solution, not an opportunity", () => { + const r = classifyOpportunityVsSolution("add a dark mode toggle"); + expect(r.isOpportunity).toBe(false); +}); + +test("multi-path statement is classified as an opportunity", () => { + const r = classifyOpportunityVsSolution("users cannot find their past orders"); + expect(r.isOpportunity).toBe(true); +}); + +test("job statement missing context fails validation", () => { + const r = validateJobStatement("I want a faster app"); + expect(r.valid).toBe(false); + expect(r.failed.length).toBeGreaterThan(0); +}); + +test("rice score is (reach*impact*confidence)/effort", () => { + expect(scoreRice({ reach: 100, impact: 2, confidence: 0.8, effort: 4 }).score).toBeCloseTo(40); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `bun test tests/product-manager-behaviour.test.ts` +Expected: FAIL - cannot find module `../src/plugins/product-manager/data.ts`. + +- [ ] **Step 3: Write `src/plugins/product-manager/data.ts`** + +```ts +// Product-Manager persona - the PM DECISION MENU, not project opinions. +// +// Carries only stable, source-cited PM craft (Cagan/SVPG, Torres, Intercom, +// Christensen, Doshi) transcribed from docs/research/2026-06-21-pm-craft-corpus.json. +// Tools surface these frameworks so the agent makes grounded value+viability +// calls before design/build. No project-specific product opinions live here. + +export interface ProductRisk { + id: "value" | "usability" | "feasibility" | "viability"; + question: string; + owner: "product-manager" | "designer" | "engineer"; +} + +export const FOUR_RISKS: ProductRisk[] = [ + { id: "value", question: "Will customers buy it, or will users choose to use it?", owner: "product-manager" }, + { id: "usability", question: "Can users figure out how to use it?", owner: "designer" }, + { id: "feasibility", question: "Can engineers build it with available time, skills, and tech?", owner: "engineer" }, + { id: "viability", question: "Does it work for the business - legal, sales, finance, marketing, support?", owner: "product-manager" }, +]; + +export const PM_OWNED_RISKS: ReadonlySet = new Set( + FOUR_RISKS.filter((r) => r.owner === "product-manager").map((r) => r.id), +); + +export interface AntiPattern { + id: string; + smell: string; + source: string; +} + +export const ANTI_PATTERNS: AntiPattern[] = [ + { id: "feature-factory", smell: "Shipping as many stakeholder features as possible. Cagan: that is 'really no product strategy at all.'", source: "Cagan/SVPG" }, + { id: "reactivity", smell: "Driven by reacting to sales, competitors, requests, or price pressure instead of a product vision.", source: "Cagan/SVPG" }, + { id: "viability-avoidance", smell: "Reasoning covers value/desirability but is silent on whether the business can sell, support, fund, or legally ship it.", source: "Cagan/SVPG" }, + { id: "execution-misdiagnosis", smell: "Treating a strategy, interpersonal, OR culture problem as an execution problem and band-aiding it.", source: "Doshi" }, + { id: "opinion-requirement", smell: "A requirement justified by 'a customer said they want X' (opinion/hypothetical) instead of an observed past-behaviour story.", source: "Torres / The Mom Test" }, +]; + +export const DISCOVERY_RULES: string[] = [ + "Engage customers continuously (aspirationally weekly); minimize build decisions made without customer evidence.", + "Never ask customers what they want or need - cognitive biases make opinion answers unreliable.", + "Elicit needs, pains, and desires through specific PAST-BEHAVIOUR stories.", + "Demographics do not predict intent; the JOB the customer hires the product for does.", +]; + +export const STRATEGY_RULES: string[] = [ + "Strategy decides which problems to solve NOW by focusing on a FEW (2-3) critical business levers.", + "Prioritization is an act of saying NO: focus means rejecting the hundred other good ideas.", + "A 'strategy' that is a long list rejecting nothing is not a strategy.", +]; + +export interface Jtbd { + definition: string; + dimensions: string[]; + fourForces: { push: string[]; resist: string[]; rule: string }; +} + +export const JTBD: Jtbd = { + definition: "A job is the PROGRESS a person wants to make under specific circumstances - a process, not a single action.", + dimensions: ["functional", "emotional", "social"], + fourForces: { + push: ["frustration with the status quo (F1)", "attraction to the new solution (F2)"], + resist: ["anxiety of learning the new (F3)", "habit/comfort of the current solution (F4)"], + rule: "A switch happens only when Push + Pull > Anxiety + Habit.", + }, +}; + +// JTBD job-statement validator - the regex-checkable SUBSET (context, progress, +// not-a-single-solution). The full 7-point checklist (e.g. "not a trend", +// "appropriate abstraction") is not mechanically testable and is a Phase 2 item. +export interface JobCheck { id: string; test: (s: string) => boolean; failHint: string; } + +export const JOB_CHECKS: JobCheck[] = [ + { id: "has-context", test: (s) => /\b(when|while|after|before|during|because)\b/i.test(s), failHint: "missing context (when/while/because ...)" }, + { id: "not-single-solution", test: (s) => !/\b(button|toggle|page|dropdown|API|endpoint|screen)\b/i.test(s), failHint: "names a specific solution, not a job" }, + { id: "expresses-progress", test: (s) => /\b(so that|in order to|to|need|want to)\b/i.test(s), failHint: "does not express the progress sought" }, +]; + +export interface JobValidation { valid: boolean; passed: string[]; failed: { id: string; failHint: string }[]; } + +export function validateJobStatement(statement: string): JobValidation { + const passed: string[] = []; + const failed: { id: string; failHint: string }[] = []; + for (const c of JOB_CHECKS) { + if (c.test(statement)) passed.push(c.id); + else failed.push({ id: c.id, failHint: c.failHint }); + } + return { valid: failed.length === 0, passed, failed }; +} + +export interface OppClassification { isOpportunity: boolean; reason: string; } + +// Torres test: "is there more than one way to address this?" If a statement +// names a single concrete mechanism, it is a solution disguised as a problem. +const SOLUTION_MARKERS = /\b(add|build|create|implement|use|toggle|button|dropdown|dark mode|integrate)\b/i; + +export function classifyOpportunityVsSolution(statement: string): OppClassification { + if (SOLUTION_MARKERS.test(statement)) { + return { isOpportunity: false, reason: "Names a single concrete mechanism. Restate as the underlying need it serves (there should be more than one way to address it)." }; + } + return { isOpportunity: true, reason: "Stated as a need/pain with more than one possible solution." }; +} + +export interface RiceInput { reach: number; impact: number; confidence: number; effort: number; } +export interface RiceResult { score: number; formula: string; } + +export function scoreRice(i: RiceInput): RiceResult { + const score = (i.reach * i.impact * i.confidence) / i.effort; + return { score, formula: "(Reach x Impact x Confidence) / Effort" }; +} +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `bun test tests/product-manager-behaviour.test.ts` +Expected: PASS (6 tests). + +- [ ] **Step 5: Commit** + +```bash +git add src/plugins/product-manager/data.ts tests/product-manager-behaviour.test.ts +git commit -m "feat(product-manager): plugin data layer - four risks, JTBD, RICE, validators" +``` + +--- + +### Task A2: Lookup tools (`get_four_risks`, `get_jtbd`, `get_discovery_rules`, `get_anti_patterns`, `get_strategy_rules`) + +These five tools share one shape: format a `data.ts` export into markdown. Each is its own file and `register()`. + +**Files:** +- Create: `src/plugins/product-manager/tools/get-four-risks.ts` +- Create: `src/plugins/product-manager/tools/get-jtbd.ts` +- Create: `src/plugins/product-manager/tools/get-discovery-rules.ts` +- Create: `src/plugins/product-manager/tools/get-anti-patterns.ts` +- Create: `src/plugins/product-manager/tools/get-strategy-rules.ts` +- Test: `tests/product-manager-behaviour.test.ts` (append) + +- [ ] **Step 1: Append failing tests** + +```ts +import { captureTool, extractTextContent } from "./helpers.ts"; +import { register as getFourRisks } from "../src/plugins/product-manager/tools/get-four-risks.ts"; +import { register as getAntiPatterns } from "../src/plugins/product-manager/tools/get-anti-patterns.ts"; + +test("get_four_risks names value and viability as PM-owned", async () => { + const tool = captureTool(getFourRisks); + expect(tool.name).toBe("product_manager_get_four_risks"); + const text = extractTextContent(await tool.invoke({})); + expect(text).toMatch(/value/i); + expect(text).toMatch(/viability/i); + expect(text).toMatch(/product-manager/); +}); + +test("get_anti_patterns includes the feature-factory smell", async () => { + const tool = captureTool(getAntiPatterns); + const text = extractTextContent(await tool.invoke({})); + expect(text).toMatch(/feature-factory/); +}); +``` + +- [ ] **Step 2: Run to verify fail** + +Run: `bun test tests/product-manager-behaviour.test.ts` +Expected: FAIL - cannot find the tool modules. + +- [ ] **Step 3: Implement the five tools** + +`src/plugins/product-manager/tools/get-four-risks.ts`: + +```ts +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { FOUR_RISKS } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_get_four_risks", + "The four product risks every build must address before shipping (Cagan/SVPG): value, usability, feasibility, viability - with which role owns each. The PM owns value and viability.", + {}, + async () => { + let text = `# The Four Product Risks\n\nAddress all four before building. The PM is accountable for **value** and **viability**.\n\n| risk | question | owner |\n|---|---|---|\n`; + for (const r of FOUR_RISKS) text += `| ${r.id} | ${r.question} | ${r.owner} |\n`; + return { content: [{ type: "text" as const, text }] }; + }, + ); +} +``` + +`src/plugins/product-manager/tools/get-anti-patterns.ts`: + +```ts +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { ANTI_PATTERNS } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_get_anti_patterns", + "Codifiable PM red flags (Cagan, Doshi): feature-factory, reactivity, viability-avoidance, execution-misdiagnosis, opinion-requirement. Use to flag weak product reasoning.", + {}, + async () => { + let text = `# PM Anti-Patterns (red flags)\n\n`; + for (const a of ANTI_PATTERNS) text += `## ${a.id}\n${a.smell} \n_source: ${a.source}_\n\n`; + return { content: [{ type: "text" as const, text }] }; + }, + ); +} +``` + +`src/plugins/product-manager/tools/get-jtbd.ts`: + +```ts +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { JTBD } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_get_jtbd", + "Jobs-To-Be-Done framing (Christensen): a job is progress-in-context across functional/emotional/social dimensions, plus the Four Forces switching rule. Use to reframe a feature request as the underlying job.", + {}, + async () => { + const f = JTBD.fourForces; + const text = + `# Jobs To Be Done\n\n${JTBD.definition}\n\n` + + `**Dimensions:** ${JTBD.dimensions.join(", ")}\n\n` + + `## Four Forces of Progress\n- Push: ${f.push.join("; ")}\n- Resist: ${f.resist.join("; ")}\n\n**${f.rule}**\n`; + return { content: [{ type: "text" as const, text }] }; + }, + ); +} +``` + +`src/plugins/product-manager/tools/get-discovery-rules.ts`: + +```ts +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { DISCOVERY_RULES } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_get_discovery_rules", + "Continuous-discovery rules (Torres): weekly customer contact, never ask 'what do you want', elicit past-behaviour stories. Use before claiming a build is customer-grounded.", + {}, + async () => { + const text = `# Discovery Rules\n\n${DISCOVERY_RULES.map((r) => `- ${r}`).join("\n")}\n`; + return { content: [{ type: "text" as const, text }] }; + }, + ); +} +``` + +`src/plugins/product-manager/tools/get-strategy-rules.ts`: + +```ts +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { STRATEGY_RULES } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_get_strategy_rules", + "Product-strategy rules (Cagan): focus on 2-3 levers, saying no is the act of prioritization, a long list is a non-strategy. Use to cut scope.", + {}, + async () => { + const text = `# Strategy Rules\n\n${STRATEGY_RULES.map((r) => `- ${r}`).join("\n")}\n`; + return { content: [{ type: "text" as const, text }] }; + }, + ); +} +``` + +- [ ] **Step 4: Run to verify pass** + +Run: `bun test tests/product-manager-behaviour.test.ts` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add src/plugins/product-manager/tools/get-four-risks.ts src/plugins/product-manager/tools/get-jtbd.ts src/plugins/product-manager/tools/get-discovery-rules.ts src/plugins/product-manager/tools/get-anti-patterns.ts src/plugins/product-manager/tools/get-strategy-rules.ts tests/product-manager-behaviour.test.ts +git commit -m "feat(product-manager): five framework-lookup tools" +``` + +--- + +### Task A3: Logic tools (`opportunity_vs_solution`, `validate_job_statement`, `score_rice`) + +**Files:** +- Create: `src/plugins/product-manager/tools/opportunity-vs-solution.ts` +- Create: `src/plugins/product-manager/tools/validate-job-statement.ts` +- Create: `src/plugins/product-manager/tools/score-rice.ts` +- Test: `tests/product-manager-behaviour.test.ts` (append) + +- [ ] **Step 1: Append failing tests** + +```ts +import { register as oppVsSol } from "../src/plugins/product-manager/tools/opportunity-vs-solution.ts"; +import { register as scoreRiceTool } from "../src/plugins/product-manager/tools/score-rice.ts"; + +test("opportunity_vs_solution flags a solution-shaped statement", async () => { + const tool = captureTool(oppVsSol); + expect(tool.name).toBe("product_manager_opportunity_vs_solution"); + const text = extractTextContent(await tool.invoke({ statement: "add a dark mode toggle" })); + expect(text).toMatch(/solution/i); +}); + +test("score_rice renders the computed score", async () => { + const tool = captureTool(scoreRiceTool); + const text = extractTextContent(await tool.invoke({ reach: 100, impact: 2, confidence: 0.8, effort: 4 })); + expect(text).toMatch(/40/); +}); +``` + +- [ ] **Step 2: Run to verify fail** + +Run: `bun test tests/product-manager-behaviour.test.ts` +Expected: FAIL - modules not found. + +- [ ] **Step 3: Implement the three tools** + +`src/plugins/product-manager/tools/opportunity-vs-solution.ts`: + +```ts +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { classifyOpportunityVsSolution } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_opportunity_vs_solution", + "Torres test: is a statement a real opportunity (a need with more than one way to address it) or a solution in disguise? Rejects premature solution-jumping.", + { statement: z.string().describe("The problem/feature statement to classify.") }, + async ({ statement }) => { + const r = classifyOpportunityVsSolution(statement); + const verdict = r.isOpportunity ? "OPPORTUNITY" : "SOLUTION (reframe needed)"; + return { content: [{ type: "text" as const, text: `# ${verdict}\n\n**Statement:** ${statement}\n\n${r.reason}\n` }] }; + }, + ); +} +``` + +`src/plugins/product-manager/tools/validate-job-statement.ts`: + +```ts +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { validateJobStatement } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_validate_job_statement", + "Validate a JTBD job statement against the falsifiable checklist (has context, expresses progress, not a single solution). Returns pass/fail per check.", + { statement: z.string().describe("The job statement to validate.") }, + async ({ statement }) => { + const r = validateJobStatement(statement); + let text = `# Job statement: ${r.valid ? "VALID" : "INVALID"}\n\n**Statement:** ${statement}\n\n`; + text += `Passed: ${r.passed.join(", ") || "none"}\n\n`; + if (r.failed.length) text += `Failed:\n${r.failed.map((f) => `- ${f.id}: ${f.failHint}`).join("\n")}\n`; + return { content: [{ type: "text" as const, text }] }; + }, + ); +} +``` + +`src/plugins/product-manager/tools/score-rice.ts`: + +```ts +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { scoreRice } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_score_rice", + "Compute a RICE prioritization score = (Reach x Impact x Confidence) / Effort (Intercom). Confidence is 0-1. Use to rank competing initiatives.", + { + reach: z.number().describe("People affected per time period."), + impact: z.number().describe("Per-person impact (e.g. 3=massive,2=high,1=medium,0.5=low,0.25=minimal)."), + confidence: z.number().describe("Confidence 0-1 (1.0=high, 0.8=medium, 0.5=low)."), + effort: z.number().describe("Person-months (or any consistent effort unit)."), + }, + async ({ reach, impact, confidence, effort }) => { + const r = scoreRice({ reach, impact, confidence, effort }); + return { content: [{ type: "text" as const, text: `# RICE score: ${r.score.toFixed(2)}\n\n\`${r.formula}\` = (${reach} x ${impact} x ${confidence}) / ${effort}\n` }] }; + }, + ); +} +``` + +- [ ] **Step 4: Run to verify pass** + +Run: `bun test tests/product-manager-behaviour.test.ts` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add src/plugins/product-manager/tools/opportunity-vs-solution.ts src/plugins/product-manager/tools/validate-job-statement.ts src/plugins/product-manager/tools/score-rice.ts tests/product-manager-behaviour.test.ts +git commit -m "feat(product-manager): logic tools - opportunity test, job validator, RICE" +``` + +--- + +### Task A4: Orchestrator tool (`resolve_product_decision`) + plugin index + wiring + +**Files:** +- Create: `src/plugins/product-manager/tools/resolve-product-decision.ts` +- Create: `src/plugins/product-manager/index.ts` +- Modify: `src/index.ts` (import + `allPlugins`) +- Modify: `tests/plugin-registry-behaviour.test.ts:23` (13 -> 14) and add PM presence test +- Test: `tests/product-manager-behaviour.test.ts` (append) + +- [ ] **Step 1: Append failing tests** + +In `tests/product-manager-behaviour.test.ts`: + +```ts +import { register as resolveDecision } from "../src/plugins/product-manager/tools/resolve-product-decision.ts"; + +test("resolve_product_decision emits a verdict and the four risks", async () => { + const tool = captureTool(resolveDecision); + expect(tool.name).toBe("product_manager_resolve_product_decision"); + const text = extractTextContent(await tool.invoke({ description: "add a dark mode toggle" })); + expect(text).toMatch(/VERDICT|NEEDS-INPUT|BLOCK|PASS/); + expect(text).toMatch(/viability/i); +}); +``` + +In `tests/plugin-registry-behaviour.test.ts`, change the count assertion and add a presence test: + +```ts +test("all 14 plugins register at least one tool", () => { + const tools = getRegisteredTools(); + const pluginPrefixes = new Set(tools.map((t) => t.name.split("_")[0])); + expect(pluginPrefixes.size).toBe(14); +}); + +test("product-manager plugin registers its gate tools", () => { + const tools = getRegisteredTools(); + const toolNames = new Set(tools.map((t) => t.name)); + expect(toolNames.has("product_manager_get_four_risks")).toBe(true); + expect(toolNames.has("product_manager_resolve_product_decision")).toBe(true); +}); +``` + +(Delete the old `"all 13 plugins..."` test block - it is replaced by the 14 version above.) + +- [ ] **Step 2: Run to verify fail** + +Run: `bun test tests/product-manager-behaviour.test.ts tests/plugin-registry-behaviour.test.ts` +Expected: FAIL - orchestrator module missing; registry still sees 13. + +- [ ] **Step 3a: Implement the orchestrator** + +`src/plugins/product-manager/tools/resolve-product-decision.ts`: + +```ts +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { FOUR_RISKS, classifyOpportunityVsSolution } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_resolve_product_decision", + "The PM gate engine. Takes a build/feature description and returns a structured product decision: opportunity framing, four-risks assessment (esp value+viability), and a PASS/BLOCK/NEEDS-INPUT verdict the gate enforces.", + { + description: z.string().describe("What is being proposed to build."), + isNetNew: z.boolean().optional().describe("True for a net-new feature/product (hard gate); false for a tweak (advisory)."), + }, + async ({ description, isNetNew }) => { + const opp = classifyOpportunityVsSolution(description); + let text = `# Product Decision\n\n**Proposal:** ${description}\n\n`; + text += `## 1. Opportunity framing\n${opp.isOpportunity ? "Framed as an opportunity." : "SOLUTION in disguise - reframe to the underlying need."} ${opp.reason}\n\n`; + text += `## 2. Four-risks assessment (fill before proceeding)\n`; + for (const r of FOUR_RISKS) text += `- **${r.id}** (${r.owner}): ${r.question} -> _unassessed_\n`; + text += `\n## 3. Verdict\n`; + const gate = isNetNew === false ? "ADVISORY" : "HARD"; + if (!opp.isOpportunity) { + text += `NEEDS-INPUT (${gate}): restate as an opportunity, then assess value + viability before build.\n`; + } else { + text += `NEEDS-INPUT (${gate}): assess all four risks - especially value and viability - and state the prioritized call with a rationale. PASS only when value+viability are addressed.\n`; + } + return { content: [{ type: "text" as const, text }] }; + }, + ); +} +``` + +- [ ] **Step 3b: Implement the plugin index** + +`src/plugins/product-manager/index.ts`: + +```ts +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { Plugin } from "../../registry.js"; +import { register as getFourRisks } from "./tools/get-four-risks.js"; +import { register as getJtbd } from "./tools/get-jtbd.js"; +import { register as getDiscoveryRules } from "./tools/get-discovery-rules.js"; +import { register as getAntiPatterns } from "./tools/get-anti-patterns.js"; +import { register as getStrategyRules } from "./tools/get-strategy-rules.js"; +import { register as opportunityVsSolution } from "./tools/opportunity-vs-solution.js"; +import { register as validateJobStatement } from "./tools/validate-job-statement.js"; +import { register as scoreRice } from "./tools/score-rice.js"; +import { register as resolveProductDecision } from "./tools/resolve-product-decision.js"; + +function register(server: McpServer): void { + getFourRisks(server); + getJtbd(server); + getDiscoveryRules(server); + getAntiPatterns(server); + getStrategyRules(server); + opportunityVsSolution(server); + validateJobStatement(server); + scoreRice(server); + resolveProductDecision(server); +} + +export const productManagerPlugin: Plugin = { + name: "product-manager", + register, +}; +``` + +- [ ] **Step 3c: Wire into `src/index.ts`** + +Add the import beside the other plugin imports: + +```ts +import { productManagerPlugin } from "./plugins/product-manager/index.js"; +``` + +Add to the `allPlugins` array (after `optimizerPlugin`): + +```ts + optimizerPlugin, + productManagerPlugin, +]; +``` + +- [ ] **Step 4: Run the full suite** + +Run: `bun test` +Expected: PASS, including the updated `all 14 plugins` and PM presence tests. + +- [ ] **Step 5: Commit** + +```bash +git add src/plugins/product-manager/tools/resolve-product-decision.ts src/plugins/product-manager/index.ts src/index.ts tests/plugin-registry-behaviour.test.ts tests/product-manager-behaviour.test.ts +git commit -m "feat(product-manager): orchestrator gate tool + register plugin (14th)" +``` + +**Part A checkpoint:** the `product-manager` plugin ships 9 callable tools. Reviewable independently. + +--- + +# PART B - `personas/` vertical infrastructure + +### Task B1: Manifest schema + registry + +**Files:** +- Create: `personas/persona.schema.json` +- Create: `personas/persona-registry.ts` +- Test: `tests/persona-registry-behaviour.test.ts` + +- [ ] **Step 1: Write the failing test** + +`tests/persona-registry-behaviour.test.ts`: + +```ts +import { test, expect } from "bun:test"; +import { loadPersonas } from "../personas/persona-registry.ts"; + +test("registry loads the product-manager persona manifest", () => { + const personas = loadPersonas(); + const pm = personas.find((p) => p.id === "product-manager"); + expect(pm).toBeDefined(); + expect(pm!.owns.risks.sort()).toEqual(["value", "viability"]); + expect(pm!.gate_policy.net_new).toBe("hard"); +}); + +test("a malformed manifest is skipped, not thrown", () => { + expect(() => loadPersonas()).not.toThrow(); +}); +``` + +- [ ] **Step 2: Run to verify fail** + +Run: `bun test tests/persona-registry-behaviour.test.ts` +Expected: FAIL - `personas/persona-registry.ts` missing (and the manifest, created in Part C, also missing - this test fully passes after Task C1; it asserts the registry mechanism here). + +- [ ] **Step 3a: Write the schema** + +`personas/persona.schema.json`: + +```json +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Hyperstack Persona Manifest", + "type": "object", + "required": ["id", "name", "version", "owns", "engages_when", "gate_policy"], + "properties": { + "id": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" }, + "name": { "type": "string" }, + "version": { "type": "string" }, + "owns": { + "type": "object", + "required": ["risks", "plugin", "skills", "agent"], + "properties": { + "risks": { "type": "array", "items": { "type": "string" } }, + "plugin": { "type": "string" }, + "skills": { "type": "array", "items": { "type": "string" } }, + "agent": { "type": "string" } + } + }, + "engages_when": { "type": "array", "items": { "type": "string" } }, + "gate_policy": { + "type": "object", + "required": ["net_new", "tweak", "override"], + "properties": { + "net_new": { "enum": ["hard", "advisory"] }, + "tweak": { "enum": ["hard", "advisory"] }, + "override": { "type": "string" } + } + } + } +} +``` + +- [ ] **Step 3b: Write the registry** + +`personas/persona-registry.ts`: + +```ts +import { readdirSync, readFileSync, existsSync, statSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const personasDir = dirname(fileURLToPath(import.meta.url)); + +export interface PersonaManifest { + id: string; + name: string; + version: string; + owns: { risks: string[]; plugin: string; skills: string[]; agent: string }; + engages_when: string[]; + gate_policy: { net_new: "hard" | "advisory"; tweak: "hard" | "advisory"; override: string }; +} + +function isValid(m: unknown): m is PersonaManifest { + const p = m as Partial; + return !!p && typeof p.id === "string" && !!p.owns && Array.isArray(p.owns.risks) && !!p.gate_policy; +} + +export function loadPersonas(): PersonaManifest[] { + const out: PersonaManifest[] = []; + for (const entry of readdirSync(personasDir)) { + const manifestPath = join(personasDir, entry, "persona.json"); + if (!existsSync(manifestPath) || !statSync(join(personasDir, entry)).isDirectory()) continue; + try { + const parsed = JSON.parse(readFileSync(manifestPath, "utf8")); + if (isValid(parsed)) out.push(parsed); + else console.error(`Persona manifest invalid, skipped: ${manifestPath}`); + } catch (err) { + console.error(`Persona manifest unreadable, skipped: ${manifestPath}:`, err); + } + } + return out; +} +``` + +- [ ] **Step 4: Run** + +Run: `bun test tests/persona-registry-behaviour.test.ts` +Expected: the "malformed skipped / not throw" test PASSES; the "loads product-manager" test FAILS until Task C1 creates the manifest. That is expected sequencing - leave it failing, it goes green in Part C. + +- [ ] **Step 5: Commit** + +```bash +git add personas/persona.schema.json personas/persona-registry.ts tests/persona-registry-behaviour.test.ts +git commit -m "feat(personas): manifest schema + resilient persona registry" +``` + +--- + +### Task B2: Bootstrap `Layer 4: Personas` (SKILL.md + compiler + regen) + +**Files:** +- Modify: `skills/hyperstack/SKILL.md` (add two sections) +- Modify: `src/internal/context-compiler.ts` (extract + emit + markers) +- Modify: `generated/runtime-context/hyperstack.bootstrap.md` (regenerated, do not hand-edit) +- Test: `tests/context-compiler-behaviour.test.ts` (append) + +- [ ] **Step 1: Append the failing test** + +In `tests/context-compiler-behaviour.test.ts`, extend the inline `source` fixture (add before the closing backtick of the template) with: + +``` +## Layer 4: Personas + +- product-manager - owns value+viability; hard-gates net-new builds + +## Persona Registry + +- product-manager - PM decision gate (plugin product-manager + skill pm-gate) +``` + +Then add an assertion in the first test after the existing `expect(content.length)...` line: + +```ts + expect(content).toMatch(/Personas/); + expect(content).toMatch(/product-manager/); +``` + +- [ ] **Step 2: Run to verify fail** + +Run: `bun test tests/context-compiler-behaviour.test.ts` +Expected: FAIL - compiler does not yet extract a Personas section, and the live SKILL.md/bootstrap sync test fails once you edit SKILL.md (Step 3b/3c order matters; do all edits then regen). + +- [ ] **Step 3a: Add sections to `skills/hyperstack/SKILL.md`** + +Insert after the `## Disallowed Transitions` section (before `## The Rationalization Catalog`): + +```markdown +## Layer 4: Personas + +Personas are judgment lenses that OWN a class of decision and gate it before +execution. They are internal and auto-engaged by `hyper`. + +| Persona | Owns | Gate | +|---|---|---| +| `product-manager` | value + viability product risk | hard for net-new builds, advisory for tweaks, user override always | + +## Persona Registry + +- `product-manager` - grounds build decisions in validated customer problems + (opportunity-vs-solution, four risks, RICE), owns value+viability, hands back + to `hyper`. Engaged before design/build on net-new feature/product/scope work. +``` + +- [ ] **Step 3b: Extend the compiler** (`src/internal/context-compiler.ts`) + +Add two markers to `REQUIRED_BOOTSTRAP_MARKERS` (after the `"website-builder"` entry): + +```ts + "Layer 4: Personas", + "product-manager", +``` + +In `compileUsingHyperstackBootstrap`, after the `roleRegistry` line, add: + +```ts + const personas = extractSimpleBullets(extractSection(body, "Persona Registry")); +``` + +In the `content` array, insert a Personas block after the Internal Roles block (after the `...roleRegistry,` group and its trailing `"",`): + +```ts + "## Personas", + "- Personas are internal judgment lenses that own and gate a decision class.", + ...personas, + "", +``` + +- [ ] **Step 3c: Regenerate the bootstrap artifact** + +Run: `bun run compile:context` +Expected stdout: "Compiled runtime context artifacts: - .../hyperstack.bootstrap.md" and a char-savings line. This rewrites `generated/runtime-context/hyperstack.bootstrap.md` so the sync test matches. + +- [ ] **Step 4: Run the full suite** + +Run: `bun test` +Expected: PASS, including `generated bootstrap artifact stays in sync` and the new Personas assertions. + +- [ ] **Step 5: Commit** + +```bash +git add skills/hyperstack/SKILL.md src/internal/context-compiler.ts generated/runtime-context/hyperstack.bootstrap.md tests/context-compiler-behaviour.test.ts +git commit -m "feat(personas): compile Layer 4 Personas into the runtime bootstrap" +``` + +**Part B checkpoint:** the bootstrap now carries a Personas layer; the registry loads manifests. Reviewable independently (PM-manifest test still pending Part C). + +--- + +# PART C - `pm-gate` skill + persona binding + router + +### Task C1: Persona manifest + role contracts + +**Files:** +- Create: `personas/product-manager/persona.json` +- Create: `personas/product-manager/PROFILE.md` +- Create: `personas/product-manager/LIFECYCLE.md` +- Create: `personas/product-manager/CHECKS.md` +- Create: `personas/README.md` + +- [ ] **Step 1: Create the manifest** `personas/product-manager/persona.json` + +```json +{ + "id": "product-manager", + "name": "Product Manager", + "version": "0.1.0", + "owns": { + "risks": ["value", "viability"], + "plugin": "product-manager", + "skills": ["pm-gate"], + "agent": "product-manager" + }, + "engages_when": ["net-new feature", "new product", "build request", "scope decision"], + "gate_policy": { "net_new": "hard", "tweak": "advisory", "override": "user-explicit" } +} +``` + +- [ ] **Step 2: Create `personas/product-manager/PROFILE.md`** + +```markdown +--- +id: product-manager +kind: persona +owns: + - value risk (will users choose it) + - viability risk (does it work for the business) +delegates_to: + - hyper +must_not_do: + - approve a net-new build without value + viability addressed + - ask customers what they want instead of eliciting past-behaviour stories + - let a solution masquerade as a validated problem +--- + +# Product Manager Persona + +## Mission + +Ground every build decision in a validated customer problem, make the prioritized +call, and own value + viability before design or engineering begins. + +## Voice + +Opinionated and evidence-backed. States a recommendation with a rationale, never +hedges with "here are five options". Cites the customer problem and the business +case. Says no to good ideas that are not the lever. + +## Authority + +- Owns the value and viability product risks (Cagan four-risks split). +- Engaged by `hyper` before specialists on net-new build/scope work. +- Hands back to `hyper` for routing, verification, and ship-gate. Never self-ships. +``` + +- [ ] **Step 3: Create `personas/product-manager/LIFECYCLE.md`** + +```markdown +# Product Manager Persona Lifecycle + +## Engage when +- net-new feature, new product, build request, or scope decision (hard gate) +- tweak/bugfix/refactor (advisory only) + +## Gate steps +1. Frame: `product_manager_opportunity_vs_solution` - reject solutions-in-disguise. +2. Ground: confirm customer evidence; flag opinion-requirements (`get_discovery_rules`). +3. Assess: `product_manager_get_four_risks` - especially value + viability. +4. Prioritize: `product_manager_score_rice` / strategy rules - state the one call. +5. Emit: `product_manager_resolve_product_decision` -> PASS | BLOCK | NEEDS-INPUT + rationale. + +## Verdicts +- PASS: value + viability addressed, prioritized call has a rationale -> hand to `hyper`. +- BLOCK: hard gate, a required risk unaddressed -> stop, report what is missing. +- NEEDS-INPUT: ambiguity -> ask the user; never silently pass. + +## Override +- User may explicitly say "skip PM" -> honour, log the override in the decision trail. + +## Handback +- Always return to `hyper` for routing and ship-gate. The persona never delivers. +``` + +- [ ] **Step 4: Create `personas/product-manager/CHECKS.md`** + +```markdown +# Product Manager Gate Checks (falsifiable) + +- [ ] Statement is an OPPORTUNITY, not a solution in disguise (more than one way to address it). +- [ ] Customer evidence exists; no requirement rests on "they said they want X". +- [ ] VALUE risk addressed: will users choose it? +- [ ] VIABILITY risk addressed: can the business sell/support/fund/legally ship it? +- [ ] A single prioritized call is stated, with a rationale (not a feature list). +- [ ] Scope cut: what is explicitly NOT being built and why. +``` + +- [ ] **Step 5: Create `personas/README.md`** + +```markdown +# Personas (Layer 4) + +Personas are judgment lenses that own and gate a class of decision before +execution. Each persona binds an MCP plugin (ground-truth), one or more skills +(process + gate), and a role identity via `persona.json`. The persona registry +(`persona-registry.ts`) loads manifests; the bootstrap compiles a Personas layer +so `hyper` knows which personas exist and when they engage. + +| Persona | Owns | First | +|---|---|---| +| `product-manager` | value + viability product risk | yes | +``` + +- [ ] **Step 6: Run the registry test (now goes green)** + +Run: `bun test tests/persona-registry-behaviour.test.ts` +Expected: PASS - both tests, including "loads the product-manager persona manifest". + +- [ ] **Step 7: Commit** + +```bash +git add personas/product-manager/ personas/README.md +git commit -m "feat(personas): product-manager manifest + role contracts" +``` + +--- + +### Task C2: `pm-gate` skill + +**Files:** +- Create: `skills/pm-gate/SKILL.md` +- Test: `tests/skills-index-behaviour.test.ts` (verify it still passes / regenerate index if needed) + +- [ ] **Step 1: Create `skills/pm-gate/SKILL.md`** + +```markdown +--- +name: pm-gate +description: Use BEFORE any net-new feature, product, or scope decision - the product-manager persona gate. Grounds the build in a validated customer problem (opportunity-vs-solution, four risks, prioritized call) and emits PASS/BLOCK/NEEDS-INPUT before design or engineering. Advisory for tweaks; user can override. +category: core +--- + +# PM Gate + +The product-manager persona's gate. It owns the value and viability product risks +that nothing else in Hyperstack owns. It runs BEFORE designer/blueprint on net-new +work. + +## Iron Law + +``` +NO NET-NEW BUILD WITHOUT A PASSED PRODUCT DECISION +(value + viability addressed, prioritized call stated with a rationale) +``` + +## When + +| Request | Gate | +|---|---| +| net-new feature / product / scope decision | HARD - must PASS before design/build | +| tweak / bugfix / refactor | ADVISORY - emit brief, do not block | +| user says "skip PM" | OVERRIDE - honour, log it | + +## Steps (MCP-grounded - call the tools, do not reason from memory) + +1. `product_manager_opportunity_vs_solution(statement)` - reject a solution-in-disguise. +2. `product_manager_get_discovery_rules()` - confirm customer evidence; flag opinion-requirements. +3. `product_manager_get_four_risks()` - assess all four, especially VALUE and VIABILITY. +4. `product_manager_score_rice(...)` and `product_manager_get_strategy_rules()` - state the one call, cut scope. +5. `product_manager_resolve_product_decision(description, isNetNew)` - emit the verdict. + +## Verdict handling + +- PASS -> hand to `hyper`; proceed to designer/blueprint. +- BLOCK -> stop; report the unaddressed risk; do not route to build. +- NEEDS-INPUT -> ask the user the specific missing question. + +## Red flags (from the corpus) + +- "A customer said they want X" used as a requirement -> opinion, not evidence. +- Reasoning covers value but is silent on viability -> the named weak-PM failure. +- "Strategy" that is a long feature list rejecting nothing -> not a strategy. +- Treating a strategy/interpersonal/culture problem as an execution problem. +``` + +- [ ] **Step 2: Regenerate the skills index (if the repo tracks it)** + +Run: `bun scripts/generate-skills-index.ts` (or `npm run skills:index`) +Expected: `skills/INDEX.md` regenerated to include `pm-gate` under Core. + +- [ ] **Step 3: Run the suite** + +Run: `bun test` +Expected: PASS - `skills-index-behaviour.test.ts` consistent with the regenerated index. + +- [ ] **Step 4: Commit** + +```bash +git add skills/pm-gate/SKILL.md skills/INDEX.md +git commit -m "feat(pm-gate): product-manager persona gate skill" +``` + +--- + +### Task C3: Router + transitions wiring + +**Files:** +- Modify: `harness/router.md` +- Modify: `harness/transitions.md` +- Test: `tests/role-harness-behaviour.test.ts` (run; update if it asserts an exact transition set) + +- [ ] **Step 1: Run the existing role-harness test to see current assertions** + +Run: `bun test tests/role-harness-behaviour.test.ts` +Expected: PASS (baseline before edits). Read it to learn whether it asserts an exact transition list - if it does, update the expected set in Step 3 to include the persona-gate line. + +- [ ] **Step 2: Add the persona-gate to `harness/router.md`** + +After the "## Default Rule" section, add: + +```markdown +## Persona Gate + +Before routing a net-new feature, product, or scope decision to any specialist, +`hyper` engages the `product-manager` persona gate (`pm-gate` skill). The gate must +return PASS before design/build. Tweaks/bugfixes get an advisory brief, not a block. +The user may explicitly override ("skip PM"), which is honoured and logged. +``` + +- [ ] **Step 3: Add transitions to `harness/transitions.md`** + +Under "## Allowed", add: + +```markdown +- `hyper -> product-manager persona gate (net-new build/scope)` +- `product-manager persona gate -> hyper` +``` + +Under "## Disallowed", add: + +```markdown +- `product-manager persona gate -> ship` (must hand back to hyper) +- `product-manager persona gate -> deliver` +``` + +- [ ] **Step 4: Run the full suite** + +Run: `bun test` +Expected: PASS. If `role-harness-behaviour.test.ts` pins an exact transition set, update its expected array to include the two new allowed lines. + +- [ ] **Step 5: Commit** + +```bash +git add harness/router.md harness/transitions.md tests/role-harness-behaviour.test.ts +git commit -m "feat(personas): wire product-manager gate into router + transitions" +``` + +**Part C checkpoint:** plugin + vertical + skill + role are bound; the PM gate is live in the routing contract. + +--- + +## Final verification + +- [ ] `bun test` - all suites green (registry 14, PM tools, bootstrap sync + Personas, persona registry, skills index). +- [ ] `bun run build` - `tsc --noEmit` clean. +- [ ] `bun run compile:context` - bootstrap regenerates with no diff (already committed). +- [ ] Manual: call `product_manager_resolve_product_decision({description:"add CSV export"})` and confirm a NEEDS-INPUT verdict naming value+viability. + +## Self-review notes (plan author) + +- **Spec coverage:** vertical (B) + plugin 9 tools (A) + skill + manifest + router (C) + tiered gate (C2/C3) + tests - all spec sections mapped. Four research-gap items remain Phase 2 (out of scope, per spec). +- **Type consistency:** tool names use `product_manager_*`; `data.ts` exports (`FOUR_RISKS`, `PM_OWNED_RISKS`, `classifyOpportunityVsSolution`, `validateJobStatement`, `scoreRice`) referenced identically across tasks. +- **Known coupling:** Task A4 bumps the plugin count test 13->14; Task B2 requires `compile:context` regen or the sync test fails. Both called out in-task. +- **Deferred/unverified (from spec):** the `product-manager` *agent* is realized here via persona contracts + `pm-gate` skill (prose), not a registered Claude Code subagent-type; if a discoverable agent-type is later required, that is a separate task pending verification of CC's agent-registration path. +``` \ No newline at end of file From e43cd7db01d470f7c7a02645e9cb11fc09d28358 Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Sun, 21 Jun 2026 21:12:08 +0530 Subject: [PATCH 03/16] feat(product-manager): plugin data layer - four risks, JTBD, RICE, validators --- src/plugins/product-manager/data.ts | 110 ++++++++++++++++++++++++ tests/product-manager-behaviour.test.ts | 38 ++++++++ 2 files changed, 148 insertions(+) create mode 100644 src/plugins/product-manager/data.ts create mode 100644 tests/product-manager-behaviour.test.ts diff --git a/src/plugins/product-manager/data.ts b/src/plugins/product-manager/data.ts new file mode 100644 index 0000000..9bfcc02 --- /dev/null +++ b/src/plugins/product-manager/data.ts @@ -0,0 +1,110 @@ +// Product-Manager persona - the PM DECISION MENU, not project opinions. +// +// Carries only stable, source-cited PM craft (Cagan/SVPG, Torres, Intercom, +// Christensen, Doshi) transcribed from docs/research/2026-06-21-pm-craft-corpus.json. +// Tools surface these frameworks so the agent makes grounded value+viability +// calls before design/build. No project-specific product opinions live here. + +export interface ProductRisk { + id: "value" | "usability" | "feasibility" | "viability"; + question: string; + owner: "product-manager" | "designer" | "engineer"; +} + +export const FOUR_RISKS: ProductRisk[] = [ + { id: "value", question: "Will customers buy it, or will users choose to use it?", owner: "product-manager" }, + { id: "usability", question: "Can users figure out how to use it?", owner: "designer" }, + { id: "feasibility", question: "Can engineers build it with available time, skills, and tech?", owner: "engineer" }, + { id: "viability", question: "Does it work for the business - legal, sales, finance, marketing, support?", owner: "product-manager" }, +]; + +export const PM_OWNED_RISKS: ReadonlySet = new Set( + FOUR_RISKS.filter((r) => r.owner === "product-manager").map((r) => r.id), +); + +export interface AntiPattern { + id: string; + smell: string; + source: string; +} + +export const ANTI_PATTERNS: AntiPattern[] = [ + { id: "feature-factory", smell: "Shipping as many stakeholder features as possible. Cagan: that is 'really no product strategy at all.'", source: "Cagan/SVPG" }, + { id: "reactivity", smell: "Driven by reacting to sales, competitors, requests, or price pressure instead of a product vision.", source: "Cagan/SVPG" }, + { id: "viability-avoidance", smell: "Reasoning covers value/desirability but is silent on whether the business can sell, support, fund, or legally ship it.", source: "Cagan/SVPG" }, + { id: "execution-misdiagnosis", smell: "Treating a strategy, interpersonal, OR culture problem as an execution problem and band-aiding it.", source: "Doshi" }, + { id: "opinion-requirement", smell: "A requirement justified by 'a customer said they want X' (opinion/hypothetical) instead of an observed past-behaviour story.", source: "Torres / The Mom Test" }, +]; + +export const DISCOVERY_RULES: string[] = [ + "Engage customers continuously (aspirationally weekly); minimize build decisions made without customer evidence.", + "Never ask customers what they want or need - cognitive biases make opinion answers unreliable.", + "Elicit needs, pains, and desires through specific PAST-BEHAVIOUR stories.", + "Demographics do not predict intent; the JOB the customer hires the product for does.", +]; + +export const STRATEGY_RULES: string[] = [ + "Strategy decides which problems to solve NOW by focusing on a FEW (2-3) critical business levers.", + "Prioritization is an act of saying NO: focus means rejecting the hundred other good ideas.", + "A 'strategy' that is a long list rejecting nothing is not a strategy.", +]; + +export interface Jtbd { + definition: string; + dimensions: string[]; + fourForces: { push: string[]; resist: string[]; rule: string }; +} + +export const JTBD: Jtbd = { + definition: "A job is the PROGRESS a person wants to make under specific circumstances - a process, not a single action.", + dimensions: ["functional", "emotional", "social"], + fourForces: { + push: ["frustration with the status quo (F1)", "attraction to the new solution (F2)"], + resist: ["anxiety of learning the new (F3)", "habit/comfort of the current solution (F4)"], + rule: "A switch happens only when Push + Pull > Anxiety + Habit.", + }, +}; + +// JTBD job-statement validator - the regex-checkable SUBSET (context, progress, +// not-a-single-solution). The full 7-point checklist (e.g. "not a trend", +// "appropriate abstraction") is not mechanically testable and is a Phase 2 item. +export interface JobCheck { id: string; test: (s: string) => boolean; failHint: string; } + +export const JOB_CHECKS: JobCheck[] = [ + { id: "has-context", test: (s) => /\b(when|while|after|before|during|because)\b/i.test(s), failHint: "missing context (when/while/because ...)" }, + { id: "not-single-solution", test: (s) => !/\b(button|toggle|page|dropdown|API|endpoint|screen)\b/i.test(s), failHint: "names a specific solution, not a job" }, + { id: "expresses-progress", test: (s) => /\b(so that|in order to|to|need|want to)\b/i.test(s), failHint: "does not express the progress sought" }, +]; + +export interface JobValidation { valid: boolean; passed: string[]; failed: { id: string; failHint: string }[]; } + +export function validateJobStatement(statement: string): JobValidation { + const passed: string[] = []; + const failed: { id: string; failHint: string }[] = []; + for (const c of JOB_CHECKS) { + if (c.test(statement)) passed.push(c.id); + else failed.push({ id: c.id, failHint: c.failHint }); + } + return { valid: failed.length === 0, passed, failed }; +} + +export interface OppClassification { isOpportunity: boolean; reason: string; } + +// Torres test: "is there more than one way to address this?" If a statement +// names a single concrete mechanism, it is a solution disguised as a problem. +const SOLUTION_MARKERS = /\b(add|build|create|implement|use|toggle|button|dropdown|dark mode|integrate)\b/i; + +export function classifyOpportunityVsSolution(statement: string): OppClassification { + if (SOLUTION_MARKERS.test(statement)) { + return { isOpportunity: false, reason: "Names a single concrete mechanism. Restate as the underlying need it serves (there should be more than one way to address it)." }; + } + return { isOpportunity: true, reason: "Stated as a need/pain with more than one possible solution." }; +} + +export interface RiceInput { reach: number; impact: number; confidence: number; effort: number; } +export interface RiceResult { score: number; formula: string; } + +export function scoreRice(i: RiceInput): RiceResult { + const score = (i.reach * i.impact * i.confidence) / i.effort; + return { score, formula: "(Reach x Impact x Confidence) / Effort" }; +} diff --git a/tests/product-manager-behaviour.test.ts b/tests/product-manager-behaviour.test.ts new file mode 100644 index 0000000..351cb13 --- /dev/null +++ b/tests/product-manager-behaviour.test.ts @@ -0,0 +1,38 @@ +import { test, expect } from "bun:test"; +import { + FOUR_RISKS, + classifyOpportunityVsSolution, + validateJobStatement, + scoreRice, + PM_OWNED_RISKS, +} from "../src/plugins/product-manager/data.ts"; + +test("four risks are exactly the canonical four", () => { + expect(FOUR_RISKS.map((r) => r.id).sort()).toEqual( + ["feasibility", "usability", "value", "viability"], + ); +}); + +test("PM owns exactly value and viability", () => { + expect([...PM_OWNED_RISKS].sort()).toEqual(["value", "viability"]); +}); + +test("single-path statement is classified as a solution, not an opportunity", () => { + const r = classifyOpportunityVsSolution("add a dark mode toggle"); + expect(r.isOpportunity).toBe(false); +}); + +test("multi-path statement is classified as an opportunity", () => { + const r = classifyOpportunityVsSolution("users cannot find their past orders"); + expect(r.isOpportunity).toBe(true); +}); + +test("job statement missing context fails validation", () => { + const r = validateJobStatement("I want a faster app"); + expect(r.valid).toBe(false); + expect(r.failed.length).toBeGreaterThan(0); +}); + +test("rice score is (reach*impact*confidence)/effort", () => { + expect(scoreRice({ reach: 100, impact: 2, confidence: 0.8, effort: 4 }).score).toBeCloseTo(40); +}); From 59a96c0abf9eb9b642afa48c96d51346a44abcff Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Sun, 21 Jun 2026 21:21:05 +0530 Subject: [PATCH 04/16] feat(product-manager): MCP plugin with snippet-backed corpus (Part A) 9 tools (four-risks, jtbd, discovery, anti-patterns, strategy, opportunity-vs- solution, validate-job-statement, score-rice, resolve-product-decision). Corpus prose stored as snippets/*.txt via createSnippetLoader (ecosystem pattern, not inline); logic stays in data.ts. Registered as 14th plugin; audit SOURCES entry added (editorial). bun test 59 pass, tsc clean. --- scripts/audit/sources.ts | 1 + src/index.ts | 2 + src/plugins/product-manager/data.ts | 69 ++++++++----------- src/plugins/product-manager/index.ts | 28 ++++++++ src/plugins/product-manager/loader.ts | 3 + .../anti-patterns/execution-misdiagnosis.txt | 1 + .../anti-patterns/feature-factory.txt | 1 + .../anti-patterns/opinion-requirement.txt | 1 + .../snippets/anti-patterns/reactivity.txt | 1 + .../anti-patterns/viability-avoidance.txt | 1 + .../snippets/discovery/rules.txt | 4 ++ .../product-manager/snippets/jtbd/jtbd.txt | 9 +++ .../snippets/risks/feasibility.txt | 1 + .../snippets/risks/usability.txt | 1 + .../product-manager/snippets/risks/value.txt | 1 + .../snippets/risks/viability.txt | 1 + .../snippets/strategy/rules.txt | 3 + .../tools/get-anti-patterns.ts | 15 ++++ .../tools/get-discovery-rules.ts | 14 ++++ .../product-manager/tools/get-four-risks.ts | 15 ++++ src/plugins/product-manager/tools/get-jtbd.ts | 14 ++++ .../tools/get-strategy-rules.ts | 14 ++++ .../tools/opportunity-vs-solution.ts | 16 +++++ .../tools/resolve-product-decision.ts | 29 ++++++++ .../product-manager/tools/score-rice.ts | 20 ++++++ .../tools/validate-job-statement.ts | 18 +++++ tests/plugin-registry-behaviour.test.ts | 11 ++- tests/product-manager-behaviour.test.ts | 42 +++++++++++ 28 files changed, 292 insertions(+), 44 deletions(-) create mode 100644 src/plugins/product-manager/index.ts create mode 100644 src/plugins/product-manager/loader.ts create mode 100644 src/plugins/product-manager/snippets/anti-patterns/execution-misdiagnosis.txt create mode 100644 src/plugins/product-manager/snippets/anti-patterns/feature-factory.txt create mode 100644 src/plugins/product-manager/snippets/anti-patterns/opinion-requirement.txt create mode 100644 src/plugins/product-manager/snippets/anti-patterns/reactivity.txt create mode 100644 src/plugins/product-manager/snippets/anti-patterns/viability-avoidance.txt create mode 100644 src/plugins/product-manager/snippets/discovery/rules.txt create mode 100644 src/plugins/product-manager/snippets/jtbd/jtbd.txt create mode 100644 src/plugins/product-manager/snippets/risks/feasibility.txt create mode 100644 src/plugins/product-manager/snippets/risks/usability.txt create mode 100644 src/plugins/product-manager/snippets/risks/value.txt create mode 100644 src/plugins/product-manager/snippets/risks/viability.txt create mode 100644 src/plugins/product-manager/snippets/strategy/rules.txt create mode 100644 src/plugins/product-manager/tools/get-anti-patterns.ts create mode 100644 src/plugins/product-manager/tools/get-discovery-rules.ts create mode 100644 src/plugins/product-manager/tools/get-four-risks.ts create mode 100644 src/plugins/product-manager/tools/get-jtbd.ts create mode 100644 src/plugins/product-manager/tools/get-strategy-rules.ts create mode 100644 src/plugins/product-manager/tools/opportunity-vs-solution.ts create mode 100644 src/plugins/product-manager/tools/resolve-product-decision.ts create mode 100644 src/plugins/product-manager/tools/score-rice.ts create mode 100644 src/plugins/product-manager/tools/validate-job-statement.ts diff --git a/scripts/audit/sources.ts b/scripts/audit/sources.ts index 33dd6ae..15c4482 100644 --- a/scripts/audit/sources.ts +++ b/scripts/audit/sources.ts @@ -41,5 +41,6 @@ export const SOURCES: PluginSource[] = [ { plugin: "ui-ux", editorial: true, skip: false, skills: [], packages: [] }, { plugin: "designer", editorial: true, skip: false, skills: ["designer"], packages: [] }, { plugin: "optimizer", editorial: true, skip: false, skills: ["optimizer"], packages: [] }, + { plugin: "product-manager", editorial: true, skip: false, skills: [], packages: [] }, { plugin: "hyperstack", editorial: false, skip: true, skills: [], packages: [] }, ]; diff --git a/src/index.ts b/src/index.ts index b0648fc..36cca20 100755 --- a/src/index.ts +++ b/src/index.ts @@ -16,6 +16,7 @@ import { designerPlugin } from "./plugins/designer/index.js"; import { shadcnPlugin } from "./plugins/shadcn/index.js"; import { hyperstackPlugin } from "./plugins/hyperstack/index.js"; import { optimizerPlugin } from "./plugins/optimizer/index.js"; +import { productManagerPlugin } from "./plugins/product-manager/index.js"; import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; @@ -44,6 +45,7 @@ export const allPlugins = [ shadcnPlugin, hyperstackPlugin, optimizerPlugin, + productManagerPlugin, ]; loadPlugins(server, allPlugins); diff --git a/src/plugins/product-manager/data.ts b/src/plugins/product-manager/data.ts index 9bfcc02..8449c63 100644 --- a/src/plugins/product-manager/data.ts +++ b/src/plugins/product-manager/data.ts @@ -1,9 +1,13 @@ -// Product-Manager persona - the PM DECISION MENU, not project opinions. +// Product-Manager persona - typed structure + decision LOGIC. // -// Carries only stable, source-cited PM craft (Cagan/SVPG, Torres, Intercom, -// Christensen, Doshi) transcribed from docs/research/2026-06-21-pm-craft-corpus.json. -// Tools surface these frameworks so the agent makes grounded value+viability -// calls before design/build. No project-specific product opinions live here. +// The prose corpus (risk questions, anti-pattern smells, discovery/strategy +// rules, JTBD framing) lives in snippets/*.txt and is loaded via the shared +// createSnippetLoader, identical to the other Hyperstack plugins. Only stable, +// source-cited PM craft (Cagan/SVPG, Torres, Intercom, Christensen, Doshi), +// transcribed from docs/research/2026-06-21-pm-craft-corpus.json. No +// project-specific product opinions live here. + +import { snippet } from "./loader.js"; export interface ProductRisk { id: "value" | "usability" | "feasibility" | "viability"; @@ -12,10 +16,10 @@ export interface ProductRisk { } export const FOUR_RISKS: ProductRisk[] = [ - { id: "value", question: "Will customers buy it, or will users choose to use it?", owner: "product-manager" }, - { id: "usability", question: "Can users figure out how to use it?", owner: "designer" }, - { id: "feasibility", question: "Can engineers build it with available time, skills, and tech?", owner: "engineer" }, - { id: "viability", question: "Does it work for the business - legal, sales, finance, marketing, support?", owner: "product-manager" }, + { id: "value", question: snippet("risks/value.txt"), owner: "product-manager" }, + { id: "usability", question: snippet("risks/usability.txt"), owner: "designer" }, + { id: "feasibility", question: snippet("risks/feasibility.txt"), owner: "engineer" }, + { id: "viability", question: snippet("risks/viability.txt"), owner: "product-manager" }, ]; export const PM_OWNED_RISKS: ReadonlySet = new Set( @@ -28,42 +32,23 @@ export interface AntiPattern { source: string; } -export const ANTI_PATTERNS: AntiPattern[] = [ - { id: "feature-factory", smell: "Shipping as many stakeholder features as possible. Cagan: that is 'really no product strategy at all.'", source: "Cagan/SVPG" }, - { id: "reactivity", smell: "Driven by reacting to sales, competitors, requests, or price pressure instead of a product vision.", source: "Cagan/SVPG" }, - { id: "viability-avoidance", smell: "Reasoning covers value/desirability but is silent on whether the business can sell, support, fund, or legally ship it.", source: "Cagan/SVPG" }, - { id: "execution-misdiagnosis", smell: "Treating a strategy, interpersonal, OR culture problem as an execution problem and band-aiding it.", source: "Doshi" }, - { id: "opinion-requirement", smell: "A requirement justified by 'a customer said they want X' (opinion/hypothetical) instead of an observed past-behaviour story.", source: "Torres / The Mom Test" }, -]; - -export const DISCOVERY_RULES: string[] = [ - "Engage customers continuously (aspirationally weekly); minimize build decisions made without customer evidence.", - "Never ask customers what they want or need - cognitive biases make opinion answers unreliable.", - "Elicit needs, pains, and desires through specific PAST-BEHAVIOUR stories.", - "Demographics do not predict intent; the JOB the customer hires the product for does.", +const ANTI_PATTERN_META: { id: string; source: string }[] = [ + { id: "feature-factory", source: "Cagan/SVPG" }, + { id: "reactivity", source: "Cagan/SVPG" }, + { id: "viability-avoidance", source: "Cagan/SVPG" }, + { id: "execution-misdiagnosis", source: "Doshi" }, + { id: "opinion-requirement", source: "Torres / The Mom Test" }, ]; -export const STRATEGY_RULES: string[] = [ - "Strategy decides which problems to solve NOW by focusing on a FEW (2-3) critical business levers.", - "Prioritization is an act of saying NO: focus means rejecting the hundred other good ideas.", - "A 'strategy' that is a long list rejecting nothing is not a strategy.", -]; - -export interface Jtbd { - definition: string; - dimensions: string[]; - fourForces: { push: string[]; resist: string[]; rule: string }; -} +export const ANTI_PATTERNS: AntiPattern[] = ANTI_PATTERN_META.map((m) => ({ + id: m.id, + source: m.source, + smell: snippet(`anti-patterns/${m.id}.txt`), +})); -export const JTBD: Jtbd = { - definition: "A job is the PROGRESS a person wants to make under specific circumstances - a process, not a single action.", - dimensions: ["functional", "emotional", "social"], - fourForces: { - push: ["frustration with the status quo (F1)", "attraction to the new solution (F2)"], - resist: ["anxiety of learning the new (F3)", "habit/comfort of the current solution (F4)"], - rule: "A switch happens only when Push + Pull > Anxiety + Habit.", - }, -}; +export const DISCOVERY_DOC: string = snippet("discovery/rules.txt"); +export const STRATEGY_DOC: string = snippet("strategy/rules.txt"); +export const JTBD_DOC: string = snippet("jtbd/jtbd.txt"); // JTBD job-statement validator - the regex-checkable SUBSET (context, progress, // not-a-single-solution). The full 7-point checklist (e.g. "not a trend", diff --git a/src/plugins/product-manager/index.ts b/src/plugins/product-manager/index.ts new file mode 100644 index 0000000..c31f30f --- /dev/null +++ b/src/plugins/product-manager/index.ts @@ -0,0 +1,28 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { Plugin } from "../../registry.js"; +import { register as getFourRisks } from "./tools/get-four-risks.js"; +import { register as getJtbd } from "./tools/get-jtbd.js"; +import { register as getDiscoveryRules } from "./tools/get-discovery-rules.js"; +import { register as getAntiPatterns } from "./tools/get-anti-patterns.js"; +import { register as getStrategyRules } from "./tools/get-strategy-rules.js"; +import { register as opportunityVsSolution } from "./tools/opportunity-vs-solution.js"; +import { register as validateJobStatement } from "./tools/validate-job-statement.js"; +import { register as scoreRice } from "./tools/score-rice.js"; +import { register as resolveProductDecision } from "./tools/resolve-product-decision.js"; + +function register(server: McpServer): void { + getFourRisks(server); + getJtbd(server); + getDiscoveryRules(server); + getAntiPatterns(server); + getStrategyRules(server); + opportunityVsSolution(server); + validateJobStatement(server); + scoreRice(server); + resolveProductDecision(server); +} + +export const productManagerPlugin: Plugin = { + name: "product-manager", + register, +}; diff --git a/src/plugins/product-manager/loader.ts b/src/plugins/product-manager/loader.ts new file mode 100644 index 0000000..2ee74d6 --- /dev/null +++ b/src/plugins/product-manager/loader.ts @@ -0,0 +1,3 @@ +import { createSnippetLoader } from "../../shared/loader-factory.js"; + +export const snippet = createSnippetLoader("product-manager"); diff --git a/src/plugins/product-manager/snippets/anti-patterns/execution-misdiagnosis.txt b/src/plugins/product-manager/snippets/anti-patterns/execution-misdiagnosis.txt new file mode 100644 index 0000000..9a84594 --- /dev/null +++ b/src/plugins/product-manager/snippets/anti-patterns/execution-misdiagnosis.txt @@ -0,0 +1 @@ +Treating a strategy, interpersonal, OR culture problem as an execution problem and band-aiding it. \ No newline at end of file diff --git a/src/plugins/product-manager/snippets/anti-patterns/feature-factory.txt b/src/plugins/product-manager/snippets/anti-patterns/feature-factory.txt new file mode 100644 index 0000000..22cd9bf --- /dev/null +++ b/src/plugins/product-manager/snippets/anti-patterns/feature-factory.txt @@ -0,0 +1 @@ +Shipping as many stakeholder features as possible. Cagan: that is 'really no product strategy at all.' \ No newline at end of file diff --git a/src/plugins/product-manager/snippets/anti-patterns/opinion-requirement.txt b/src/plugins/product-manager/snippets/anti-patterns/opinion-requirement.txt new file mode 100644 index 0000000..8c25eb9 --- /dev/null +++ b/src/plugins/product-manager/snippets/anti-patterns/opinion-requirement.txt @@ -0,0 +1 @@ +A requirement justified by 'a customer said they want X' (opinion/hypothetical) instead of an observed past-behaviour story. \ No newline at end of file diff --git a/src/plugins/product-manager/snippets/anti-patterns/reactivity.txt b/src/plugins/product-manager/snippets/anti-patterns/reactivity.txt new file mode 100644 index 0000000..0d28b11 --- /dev/null +++ b/src/plugins/product-manager/snippets/anti-patterns/reactivity.txt @@ -0,0 +1 @@ +Driven by reacting to sales, competitors, requests, or price pressure instead of a product vision. \ No newline at end of file diff --git a/src/plugins/product-manager/snippets/anti-patterns/viability-avoidance.txt b/src/plugins/product-manager/snippets/anti-patterns/viability-avoidance.txt new file mode 100644 index 0000000..39d535e --- /dev/null +++ b/src/plugins/product-manager/snippets/anti-patterns/viability-avoidance.txt @@ -0,0 +1 @@ +Reasoning covers value/desirability but is silent on whether the business can sell, support, fund, or legally ship it. \ No newline at end of file diff --git a/src/plugins/product-manager/snippets/discovery/rules.txt b/src/plugins/product-manager/snippets/discovery/rules.txt new file mode 100644 index 0000000..02e8537 --- /dev/null +++ b/src/plugins/product-manager/snippets/discovery/rules.txt @@ -0,0 +1,4 @@ +- Engage customers continuously (aspirationally weekly); minimize build decisions made without customer evidence. +- Never ask customers what they want or need - cognitive biases make opinion answers unreliable. +- Elicit needs, pains, and desires through specific PAST-BEHAVIOUR stories. +- Demographics do not predict intent; the JOB the customer hires the product for does. \ No newline at end of file diff --git a/src/plugins/product-manager/snippets/jtbd/jtbd.txt b/src/plugins/product-manager/snippets/jtbd/jtbd.txt new file mode 100644 index 0000000..6c0d0fa --- /dev/null +++ b/src/plugins/product-manager/snippets/jtbd/jtbd.txt @@ -0,0 +1,9 @@ +A job is the PROGRESS a person wants to make under specific circumstances - a process, not a single action. + +**Dimensions:** functional, emotional, social + +## Four Forces of Progress +- Push: frustration with the status quo (F1); attraction to the new solution (F2) +- Resist: anxiety of learning the new (F3); habit/comfort of the current solution (F4) + +**A switch happens only when Push + Pull > Anxiety + Habit.** \ No newline at end of file diff --git a/src/plugins/product-manager/snippets/risks/feasibility.txt b/src/plugins/product-manager/snippets/risks/feasibility.txt new file mode 100644 index 0000000..c8c31ee --- /dev/null +++ b/src/plugins/product-manager/snippets/risks/feasibility.txt @@ -0,0 +1 @@ +Can engineers build it with available time, skills, and tech? \ No newline at end of file diff --git a/src/plugins/product-manager/snippets/risks/usability.txt b/src/plugins/product-manager/snippets/risks/usability.txt new file mode 100644 index 0000000..40f72a2 --- /dev/null +++ b/src/plugins/product-manager/snippets/risks/usability.txt @@ -0,0 +1 @@ +Can users figure out how to use it? \ No newline at end of file diff --git a/src/plugins/product-manager/snippets/risks/value.txt b/src/plugins/product-manager/snippets/risks/value.txt new file mode 100644 index 0000000..2f14648 --- /dev/null +++ b/src/plugins/product-manager/snippets/risks/value.txt @@ -0,0 +1 @@ +Will customers buy it, or will users choose to use it? \ No newline at end of file diff --git a/src/plugins/product-manager/snippets/risks/viability.txt b/src/plugins/product-manager/snippets/risks/viability.txt new file mode 100644 index 0000000..7694a5f --- /dev/null +++ b/src/plugins/product-manager/snippets/risks/viability.txt @@ -0,0 +1 @@ +Does it work for the business - legal, sales, finance, marketing, support? \ No newline at end of file diff --git a/src/plugins/product-manager/snippets/strategy/rules.txt b/src/plugins/product-manager/snippets/strategy/rules.txt new file mode 100644 index 0000000..efdf19d --- /dev/null +++ b/src/plugins/product-manager/snippets/strategy/rules.txt @@ -0,0 +1,3 @@ +- Strategy decides which problems to solve NOW by focusing on a FEW (2-3) critical business levers. +- Prioritization is an act of saying NO: focus means rejecting the hundred other good ideas. +- A 'strategy' that is a long list rejecting nothing is not a strategy. \ No newline at end of file diff --git a/src/plugins/product-manager/tools/get-anti-patterns.ts b/src/plugins/product-manager/tools/get-anti-patterns.ts new file mode 100644 index 0000000..2fa0482 --- /dev/null +++ b/src/plugins/product-manager/tools/get-anti-patterns.ts @@ -0,0 +1,15 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { ANTI_PATTERNS } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_get_anti_patterns", + "Codifiable PM red flags (Cagan, Doshi): feature-factory, reactivity, viability-avoidance, execution-misdiagnosis, opinion-requirement. Use to flag weak product reasoning.", + {}, + async () => { + let text = `# PM Anti-Patterns (red flags)\n\n`; + for (const a of ANTI_PATTERNS) text += `## ${a.id}\n${a.smell} \n_source: ${a.source}_\n\n`; + return { content: [{ type: "text" as const, text }] }; + }, + ); +} diff --git a/src/plugins/product-manager/tools/get-discovery-rules.ts b/src/plugins/product-manager/tools/get-discovery-rules.ts new file mode 100644 index 0000000..95fd171 --- /dev/null +++ b/src/plugins/product-manager/tools/get-discovery-rules.ts @@ -0,0 +1,14 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { DISCOVERY_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_get_discovery_rules", + "Continuous-discovery rules (Torres): weekly customer contact, never ask 'what do you want', elicit past-behaviour stories. Use before claiming a build is customer-grounded.", + {}, + async () => { + const text = `# Discovery Rules\n\n${DISCOVERY_DOC}\n`; + return { content: [{ type: "text" as const, text }] }; + }, + ); +} diff --git a/src/plugins/product-manager/tools/get-four-risks.ts b/src/plugins/product-manager/tools/get-four-risks.ts new file mode 100644 index 0000000..2212d08 --- /dev/null +++ b/src/plugins/product-manager/tools/get-four-risks.ts @@ -0,0 +1,15 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { FOUR_RISKS } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_get_four_risks", + "The four product risks every build must address before shipping (Cagan/SVPG): value, usability, feasibility, viability - with which role owns each. The PM owns value and viability.", + {}, + async () => { + let text = `# The Four Product Risks\n\nAddress all four before building. The PM is accountable for **value** and **viability**.\n\n| risk | question | owner |\n|---|---|---|\n`; + for (const r of FOUR_RISKS) text += `| ${r.id} | ${r.question} | ${r.owner} |\n`; + return { content: [{ type: "text" as const, text }] }; + }, + ); +} diff --git a/src/plugins/product-manager/tools/get-jtbd.ts b/src/plugins/product-manager/tools/get-jtbd.ts new file mode 100644 index 0000000..61e6370 --- /dev/null +++ b/src/plugins/product-manager/tools/get-jtbd.ts @@ -0,0 +1,14 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { JTBD_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_get_jtbd", + "Jobs-To-Be-Done framing (Christensen): a job is progress-in-context across functional/emotional/social dimensions, plus the Four Forces switching rule. Use to reframe a feature request as the underlying job.", + {}, + async () => { + const text = `# Jobs To Be Done\n\n${JTBD_DOC}\n`; + return { content: [{ type: "text" as const, text }] }; + }, + ); +} diff --git a/src/plugins/product-manager/tools/get-strategy-rules.ts b/src/plugins/product-manager/tools/get-strategy-rules.ts new file mode 100644 index 0000000..ebd433e --- /dev/null +++ b/src/plugins/product-manager/tools/get-strategy-rules.ts @@ -0,0 +1,14 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { STRATEGY_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_get_strategy_rules", + "Product-strategy rules (Cagan): focus on 2-3 levers, saying no is the act of prioritization, a long list is a non-strategy. Use to cut scope.", + {}, + async () => { + const text = `# Strategy Rules\n\n${STRATEGY_DOC}\n`; + return { content: [{ type: "text" as const, text }] }; + }, + ); +} diff --git a/src/plugins/product-manager/tools/opportunity-vs-solution.ts b/src/plugins/product-manager/tools/opportunity-vs-solution.ts new file mode 100644 index 0000000..4dd7c83 --- /dev/null +++ b/src/plugins/product-manager/tools/opportunity-vs-solution.ts @@ -0,0 +1,16 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { classifyOpportunityVsSolution } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_opportunity_vs_solution", + "Torres test: is a statement a real opportunity (a need with more than one way to address it) or a solution in disguise? Rejects premature solution-jumping.", + { statement: z.string().describe("The problem/feature statement to classify.") }, + async ({ statement }) => { + const r = classifyOpportunityVsSolution(statement); + const verdict = r.isOpportunity ? "OPPORTUNITY" : "SOLUTION (reframe needed)"; + return { content: [{ type: "text" as const, text: `# ${verdict}\n\n**Statement:** ${statement}\n\n${r.reason}\n` }] }; + }, + ); +} diff --git a/src/plugins/product-manager/tools/resolve-product-decision.ts b/src/plugins/product-manager/tools/resolve-product-decision.ts new file mode 100644 index 0000000..464c601 --- /dev/null +++ b/src/plugins/product-manager/tools/resolve-product-decision.ts @@ -0,0 +1,29 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { FOUR_RISKS, classifyOpportunityVsSolution } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_resolve_product_decision", + "The PM gate engine. Takes a build/feature description and returns a structured product decision: opportunity framing, four-risks assessment (esp value+viability), and a PASS/BLOCK/NEEDS-INPUT verdict the gate enforces.", + { + description: z.string().describe("What is being proposed to build."), + isNetNew: z.boolean().optional().describe("True for a net-new feature/product (hard gate); false for a tweak (advisory)."), + }, + async ({ description, isNetNew }) => { + const opp = classifyOpportunityVsSolution(description); + let text = `# Product Decision\n\n**Proposal:** ${description}\n\n`; + text += `## 1. Opportunity framing\n${opp.isOpportunity ? "Framed as an opportunity." : "SOLUTION in disguise - reframe to the underlying need."} ${opp.reason}\n\n`; + text += `## 2. Four-risks assessment (fill before proceeding)\n`; + for (const r of FOUR_RISKS) text += `- **${r.id}** (${r.owner}): ${r.question} -> _unassessed_\n`; + text += `\n## 3. Verdict\n`; + const gate = isNetNew === false ? "ADVISORY" : "HARD"; + if (!opp.isOpportunity) { + text += `NEEDS-INPUT (${gate}): restate as an opportunity, then assess value + viability before build.\n`; + } else { + text += `NEEDS-INPUT (${gate}): assess all four risks - especially value and viability - and state the prioritized call with a rationale. PASS only when value+viability are addressed.\n`; + } + return { content: [{ type: "text" as const, text }] }; + }, + ); +} diff --git a/src/plugins/product-manager/tools/score-rice.ts b/src/plugins/product-manager/tools/score-rice.ts new file mode 100644 index 0000000..f0f59da --- /dev/null +++ b/src/plugins/product-manager/tools/score-rice.ts @@ -0,0 +1,20 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { scoreRice } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_score_rice", + "Compute a RICE prioritization score = (Reach x Impact x Confidence) / Effort (Intercom). Confidence is 0-1. Use to rank competing initiatives.", + { + reach: z.number().describe("People affected per time period."), + impact: z.number().describe("Per-person impact (e.g. 3=massive,2=high,1=medium,0.5=low,0.25=minimal)."), + confidence: z.number().describe("Confidence 0-1 (1.0=high, 0.8=medium, 0.5=low)."), + effort: z.number().describe("Person-months (or any consistent effort unit)."), + }, + async ({ reach, impact, confidence, effort }) => { + const r = scoreRice({ reach, impact, confidence, effort }); + return { content: [{ type: "text" as const, text: `# RICE score: ${r.score.toFixed(2)}\n\n\`${r.formula}\` = (${reach} x ${impact} x ${confidence}) / ${effort}\n` }] }; + }, + ); +} diff --git a/src/plugins/product-manager/tools/validate-job-statement.ts b/src/plugins/product-manager/tools/validate-job-statement.ts new file mode 100644 index 0000000..c1ae522 --- /dev/null +++ b/src/plugins/product-manager/tools/validate-job-statement.ts @@ -0,0 +1,18 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { validateJobStatement } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_validate_job_statement", + "Validate a JTBD job statement against the falsifiable checklist (has context, expresses progress, not a single solution). Returns pass/fail per check.", + { statement: z.string().describe("The job statement to validate.") }, + async ({ statement }) => { + const r = validateJobStatement(statement); + let text = `# Job statement: ${r.valid ? "VALID" : "INVALID"}\n\n**Statement:** ${statement}\n\n`; + text += `Passed: ${r.passed.join(", ") || "none"}\n\n`; + if (r.failed.length) text += `Failed:\n${r.failed.map((f) => `- ${f.id}: ${f.failHint}`).join("\n")}\n`; + return { content: [{ type: "text" as const, text }] }; + }, + ); +} diff --git a/tests/plugin-registry-behaviour.test.ts b/tests/plugin-registry-behaviour.test.ts index 79d25f1..a1e10c8 100644 --- a/tests/plugin-registry-behaviour.test.ts +++ b/tests/plugin-registry-behaviour.test.ts @@ -17,10 +17,17 @@ function getRegisteredTools() { return tools; } -test("all 13 plugins register at least one tool", () => { +test("all 14 plugins register at least one tool", () => { const tools = getRegisteredTools(); const pluginPrefixes = new Set(tools.map((t) => t.name.split("_")[0])); - expect(pluginPrefixes.size).toBe(13); + expect(pluginPrefixes.size).toBe(14); +}); + +test("product-manager plugin registers its gate tools", () => { + const tools = getRegisteredTools(); + const toolNames = new Set(tools.map((t) => t.name)); + expect(toolNames.has("product_manager_get_four_risks")).toBe(true); + expect(toolNames.has("product_manager_resolve_product_decision")).toBe(true); }); test("every registered tool has a non-empty name and description", () => { diff --git a/tests/product-manager-behaviour.test.ts b/tests/product-manager-behaviour.test.ts index 351cb13..cf5ce92 100644 --- a/tests/product-manager-behaviour.test.ts +++ b/tests/product-manager-behaviour.test.ts @@ -6,6 +6,12 @@ import { scoreRice, PM_OWNED_RISKS, } from "../src/plugins/product-manager/data.ts"; +import { captureTool, extractTextContent } from "./helpers.ts"; +import { register as getFourRisks } from "../src/plugins/product-manager/tools/get-four-risks.ts"; +import { register as getAntiPatterns } from "../src/plugins/product-manager/tools/get-anti-patterns.ts"; +import { register as oppVsSol } from "../src/plugins/product-manager/tools/opportunity-vs-solution.ts"; +import { register as scoreRiceTool } from "../src/plugins/product-manager/tools/score-rice.ts"; +import { register as resolveDecision } from "../src/plugins/product-manager/tools/resolve-product-decision.ts"; test("four risks are exactly the canonical four", () => { expect(FOUR_RISKS.map((r) => r.id).sort()).toEqual( @@ -36,3 +42,39 @@ test("job statement missing context fails validation", () => { test("rice score is (reach*impact*confidence)/effort", () => { expect(scoreRice({ reach: 100, impact: 2, confidence: 0.8, effort: 4 }).score).toBeCloseTo(40); }); + +test("get_four_risks names value and viability as PM-owned", async () => { + const tool = captureTool(getFourRisks); + expect(tool.name).toBe("product_manager_get_four_risks"); + const text = extractTextContent(await tool.invoke({})); + expect(text).toMatch(/value/i); + expect(text).toMatch(/viability/i); + expect(text).toMatch(/product-manager/); +}); + +test("get_anti_patterns includes the feature-factory smell", async () => { + const tool = captureTool(getAntiPatterns); + const text = extractTextContent(await tool.invoke({})); + expect(text).toMatch(/feature-factory/); +}); + +test("opportunity_vs_solution flags a solution-shaped statement", async () => { + const tool = captureTool(oppVsSol); + expect(tool.name).toBe("product_manager_opportunity_vs_solution"); + const text = extractTextContent(await tool.invoke({ statement: "add a dark mode toggle" })); + expect(text).toMatch(/solution/i); +}); + +test("score_rice renders the computed score", async () => { + const tool = captureTool(scoreRiceTool); + const text = extractTextContent(await tool.invoke({ reach: 100, impact: 2, confidence: 0.8, effort: 4 })); + expect(text).toMatch(/40/); +}); + +test("resolve_product_decision emits a verdict and the four risks", async () => { + const tool = captureTool(resolveDecision); + expect(tool.name).toBe("product_manager_resolve_product_decision"); + const text = extractTextContent(await tool.invoke({ description: "add a dark mode toggle" })); + expect(text).toMatch(/VERDICT|NEEDS-INPUT|BLOCK|PASS/); + expect(text).toMatch(/viability/i); +}); From 74f5d0089729d16c34d01a9e28c32701064e648e Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Sun, 21 Jun 2026 21:21:35 +0530 Subject: [PATCH 05/16] docs(persona): record snippet-storage refactor + sources.ts step in plan --- docs/superpowers/plans/2026-06-21-pm-persona-vertical.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/superpowers/plans/2026-06-21-pm-persona-vertical.md b/docs/superpowers/plans/2026-06-21-pm-persona-vertical.md index 2e64733..7f5d694 100644 --- a/docs/superpowers/plans/2026-06-21-pm-persona-vertical.md +++ b/docs/superpowers/plans/2026-06-21-pm-persona-vertical.md @@ -47,6 +47,15 @@ PART C - skill + binding (composes A+B) # PART A - `product-manager` MCP plugin +> **Storage (revised during execution):** corpus prose is NOT inlined in `data.ts`. +> It lives in `src/plugins/product-manager/snippets/**.txt` loaded via +> `loader.ts` (`createSnippetLoader`), matching the dominant ecosystem pattern +> (react/golang/designer). `data.ts` keeps typed structure + logic and pulls prose +> via `snippet("...")`. Also required: a `product-manager` entry in +> `scripts/audit/sources.ts` (editorial, `skills: []` until Part C sets `["pm-gate"]`), +> enforced by `tests/audit-harness-behaviour.test.ts`. See the committed plugin +> for the authoritative shape. + ### Task A1: Plugin data layer (types + frameworks + resolvers) **Files:** From 9dbedc366bc4b91a1ef29d2a2b6e422eb9e1fbfb Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Sun, 21 Jun 2026 21:23:51 +0530 Subject: [PATCH 06/16] feat(personas): manifest schema + registry (Part B1) Persona vertical: top-level personas/ holds manifests (content), src/personas/ registry.ts loads them (type-checked, reads top-level personas/ same as the context-compiler reads skills/). product-manager manifest binds plugin+skill+ role with tiered gate_policy. --- personas/persona.schema.json | 31 +++++++++++++++++ personas/product-manager/persona.json | 13 +++++++ src/personas/registry.ts | 44 ++++++++++++++++++++++++ tests/persona-registry-behaviour.test.ts | 14 ++++++++ 4 files changed, 102 insertions(+) create mode 100644 personas/persona.schema.json create mode 100644 personas/product-manager/persona.json create mode 100644 src/personas/registry.ts create mode 100644 tests/persona-registry-behaviour.test.ts diff --git a/personas/persona.schema.json b/personas/persona.schema.json new file mode 100644 index 0000000..7163052 --- /dev/null +++ b/personas/persona.schema.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Hyperstack Persona Manifest", + "type": "object", + "required": ["id", "name", "version", "owns", "engages_when", "gate_policy"], + "properties": { + "id": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" }, + "name": { "type": "string" }, + "version": { "type": "string" }, + "owns": { + "type": "object", + "required": ["risks", "plugin", "skills", "agent"], + "properties": { + "risks": { "type": "array", "items": { "type": "string" } }, + "plugin": { "type": "string" }, + "skills": { "type": "array", "items": { "type": "string" } }, + "agent": { "type": "string" } + } + }, + "engages_when": { "type": "array", "items": { "type": "string" } }, + "gate_policy": { + "type": "object", + "required": ["net_new", "tweak", "override"], + "properties": { + "net_new": { "enum": ["hard", "advisory"] }, + "tweak": { "enum": ["hard", "advisory"] }, + "override": { "type": "string" } + } + } + } +} diff --git a/personas/product-manager/persona.json b/personas/product-manager/persona.json new file mode 100644 index 0000000..6397911 --- /dev/null +++ b/personas/product-manager/persona.json @@ -0,0 +1,13 @@ +{ + "id": "product-manager", + "name": "Product Manager", + "version": "0.1.0", + "owns": { + "risks": ["value", "viability"], + "plugin": "product-manager", + "skills": ["pm-gate"], + "agent": "product-manager" + }, + "engages_when": ["net-new feature", "new product", "build request", "scope decision"], + "gate_policy": { "net_new": "hard", "tweak": "advisory", "override": "user-explicit" } +} diff --git a/src/personas/registry.ts b/src/personas/registry.ts new file mode 100644 index 0000000..0ab1317 --- /dev/null +++ b/src/personas/registry.ts @@ -0,0 +1,44 @@ +import { readdirSync, readFileSync, existsSync, statSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +// Persona manifests live in the top-level personas/ vertical (content), loaded +// from src/ here the same way the context-compiler reads top-level skills/. +const personasDir = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "personas"); + +export interface PersonaManifest { + id: string; + name: string; + version: string; + owns: { risks: string[]; plugin: string; skills: string[]; agent: string }; + engages_when: string[]; + gate_policy: { net_new: "hard" | "advisory"; tweak: "hard" | "advisory"; override: string }; +} + +function isValid(m: unknown): m is PersonaManifest { + const p = m as Partial; + return ( + !!p && + typeof p.id === "string" && + !!p.owns && + Array.isArray(p.owns.risks) && + !!p.gate_policy + ); +} + +export function loadPersonas(): PersonaManifest[] { + const out: PersonaManifest[] = []; + for (const entry of readdirSync(personasDir)) { + const dir = join(personasDir, entry); + const manifestPath = join(dir, "persona.json"); + if (!existsSync(manifestPath) || !statSync(dir).isDirectory()) continue; + try { + const parsed = JSON.parse(readFileSync(manifestPath, "utf8")); + if (isValid(parsed)) out.push(parsed); + else console.error(`Persona manifest invalid, skipped: ${manifestPath}`); + } catch (err) { + console.error(`Persona manifest unreadable, skipped: ${manifestPath}:`, err); + } + } + return out; +} diff --git a/tests/persona-registry-behaviour.test.ts b/tests/persona-registry-behaviour.test.ts new file mode 100644 index 0000000..712a880 --- /dev/null +++ b/tests/persona-registry-behaviour.test.ts @@ -0,0 +1,14 @@ +import { test, expect } from "bun:test"; +import { loadPersonas } from "../src/personas/registry.ts"; + +test("registry loads the product-manager persona manifest", () => { + const personas = loadPersonas(); + const pm = personas.find((p) => p.id === "product-manager"); + expect(pm).toBeDefined(); + expect(pm!.owns.risks.sort()).toEqual(["value", "viability"]); + expect(pm!.gate_policy.net_new).toBe("hard"); +}); + +test("a malformed manifest is skipped, not thrown", () => { + expect(() => loadPersonas()).not.toThrow(); +}); From 4b987d19eea8c0ce5c9056fe24aef4057f59be43 Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Sun, 21 Jun 2026 21:25:53 +0530 Subject: [PATCH 07/16] feat(personas): compile Layer 4 Personas into runtime bootstrap (Part B2) SKILL.md gains Layer 4 Personas + Persona Registry sections; context-compiler extracts the registry and emits a Personas block, guarded by new required markers (Personas, product-manager). Bootstrap regenerated (58.1% savings). --- .../runtime-context/hyperstack.bootstrap.md | 4 ++++ skills/hyperstack/SKILL.md | 17 +++++++++++++++++ src/internal/context-compiler.ts | 7 +++++++ tests/context-compiler-behaviour.test.ts | 5 +++++ 4 files changed, 33 insertions(+) diff --git a/generated/runtime-context/hyperstack.bootstrap.md b/generated/runtime-context/hyperstack.bootstrap.md index 87137d0..a5310a3 100644 --- a/generated/runtime-context/hyperstack.bootstrap.md +++ b/generated/runtime-context/hyperstack.bootstrap.md @@ -78,6 +78,10 @@ Hyperstack is a **Three-Layer Ecosystem**: - `hyper` - conductor, classifier, gatekeeper, verifier, and delivery owner - `website-builder` - first specialist for website-facing design and +## Personas +- Personas are internal judgment lenses that own and gate a decision class. +- `product-manager` - grounds build decisions in validated customer problems + ## Routing Summary - Every request enters through `hyper` - `hyper` inspects the workspace first: package manifests, dependency signals, diff --git a/skills/hyperstack/SKILL.md b/skills/hyperstack/SKILL.md index adf9f19..7583e0e 100644 --- a/skills/hyperstack/SKILL.md +++ b/skills/hyperstack/SKILL.md @@ -246,6 +246,23 @@ The bootstrap and orchestrator (`hyper`) choose the correct role based on the re --- +## Layer 4: Personas + +Personas are judgment lenses that OWN a class of decision and gate it before +execution. They are internal and auto-engaged by `hyper`. + +| Persona | Owns | Gate | +|---|---|---| +| `product-manager` | value + viability product risk | hard for net-new builds, advisory for tweaks, user override always | + +## Persona Registry + +- `product-manager` - grounds build decisions in validated customer problems + (opportunity-vs-solution, four risks, RICE), owns value+viability, hands back + to `hyper`. Engaged before design/build on net-new feature/product/scope work. + +--- + ## The Rationalization Catalog (Read Before Every Session) These are the exact thoughts you will have when you want to skip a skill. Every one is a bug in your reasoning. Every one has been written down because someone (probably you in a past session) used it to ship bad code. diff --git a/src/internal/context-compiler.ts b/src/internal/context-compiler.ts index ab73278..e7eb3e1 100644 --- a/src/internal/context-compiler.ts +++ b/src/internal/context-compiler.ts @@ -45,6 +45,8 @@ const REQUIRED_BOOTSTRAP_MARKERS = [ "announce it", "hyper", "website-builder", + "Personas", + "product-manager", "auto-called", "hyper -> website-builder", ]; @@ -168,6 +170,7 @@ export function compileUsingHyperstackBootstrap(source: string): { content: stri const workflowSkills = compactWorkflowTables(body); const instructionPriority = extractInstructionPriority(body); const roleRegistry = extractSimpleBullets(extractSection(body, "Role Registry")); + const personaRegistry = extractSimpleBullets(extractSection(body, "Persona Registry")); const routingSummary = extractSimpleBullets(extractSection(body, "Routing Summary")); const allowedTransitions = extractSimpleBullets(extractSection(body, "Allowed Transitions")); const disallowedTransitions = extractSimpleBullets(extractSection(body, "Disallowed Transitions")); @@ -199,6 +202,10 @@ export function compileUsingHyperstackBootstrap(source: string): { content: stri "- Roles are internal and auto-called. Users do not invoke them directly.", ...roleRegistry, "", + "## Personas", + "- Personas are internal judgment lenses that own and gate a decision class.", + ...personaRegistry, + "", "## Routing Summary", ...routingSummary, "", diff --git a/tests/context-compiler-behaviour.test.ts b/tests/context-compiler-behaviour.test.ts index ad4ca29..fe7079c 100644 --- a/tests/context-compiler-behaviour.test.ts +++ b/tests/context-compiler-behaviour.test.ts @@ -48,6 +48,9 @@ ${"x".repeat(2000)} ## Role Registry - Role 1 +## Persona Registry +- product-manager - PM gate + ## Routing Summary - Route 1 @@ -74,6 +77,8 @@ ${"x".repeat(2000)} expect(content).toMatch(/invariant-1/); expect(content).toMatch(/invariant-2/); expect(content.length).toBeLessThan(source.length); + expect(content).toMatch(/Personas/); + expect(content).toMatch(/product-manager/); }); test("generated bootstrap artifact stays in sync with the compiler output", () => { From 67439c287b9a783f77aa5064115778db5b97b04e Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Sun, 21 Jun 2026 21:27:48 +0530 Subject: [PATCH 08/16] feat(personas): pm-gate skill + role contracts + router wiring (Part C) product-manager persona contracts (PROFILE/LIFECYCLE/CHECKS), pm-gate skill (tiered: hard net-new, advisory tweaks, user override), sources.ts skill link, and hyper -> product-manager persona-gate transitions in router/transitions. --- harness/router.md | 7 ++++ harness/transitions.md | 4 +++ personas/README.md | 27 +++++++++++++++ personas/product-manager/CHECKS.md | 8 +++++ personas/product-manager/LIFECYCLE.md | 23 +++++++++++++ personas/product-manager/PROFILE.md | 37 +++++++++++++++++++++ scripts/audit/sources.ts | 2 +- skills/INDEX.md | 1 + skills/pm-gate/SKILL.md | 47 +++++++++++++++++++++++++++ 9 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 personas/README.md create mode 100644 personas/product-manager/CHECKS.md create mode 100644 personas/product-manager/LIFECYCLE.md create mode 100644 personas/product-manager/PROFILE.md create mode 100644 skills/pm-gate/SKILL.md diff --git a/harness/router.md b/harness/router.md index f04c611..35cf828 100644 --- a/harness/router.md +++ b/harness/router.md @@ -6,6 +6,13 @@ Every user request enters through `hyper`. Users do not invoke internal roles directly. Roles are internal and auto-called. +## Persona Gate + +Before routing a net-new feature, product, or scope decision to any specialist, +`hyper` engages the `product-manager` persona gate (`pm-gate` skill). The gate must +return PASS before design/build. Tweaks/bugfixes get an advisory brief, not a block. +The user may explicitly override ("skip PM"), which is honoured and logged. + ## Routing Matrix Route `hyper -> website-builder` when the request is primarily about: diff --git a/harness/transitions.md b/harness/transitions.md index a023723..310d5fc 100644 --- a/harness/transitions.md +++ b/harness/transitions.md @@ -7,6 +7,8 @@ - `website-builder -> hyper` - `hyper -> existing Hyperstack skills/plugins` - `hyper -> verification and delivery gates` +- `hyper -> product-manager persona gate (net-new build/scope)` +- `product-manager persona gate -> hyper` ## Disallowed @@ -14,6 +16,8 @@ - `website-builder -> ship` - `website-builder -> deliver` - `website-builder` claiming final completion directly +- `product-manager persona gate -> ship` (must hand back to hyper) +- `product-manager persona gate -> deliver` ## V1 Principle diff --git a/personas/README.md b/personas/README.md new file mode 100644 index 0000000..bc2bb4c --- /dev/null +++ b/personas/README.md @@ -0,0 +1,27 @@ +# Personas (Layer 4) + +Personas are judgment lenses that own and gate a class of decision before +execution. Each persona binds an MCP plugin (ground-truth), one or more skills +(process + gate), and a role identity via `persona.json`. The persona registry +(`src/personas/registry.ts`) loads manifests; the bootstrap compiles a Personas +layer so `hyper` knows which personas exist and when they engage. + +| Persona | Owns | First | +|---|---|---| +| `product-manager` | value + viability product risk | yes | + +## Anatomy of a persona + +``` +personas/ + persona.schema.json manifest contract + / + persona.json binds plugin + skills + agent + gate_policy + PROFILE.md identity, mission, voice + LIFECYCLE.md engage criteria, gate steps, handback + CHECKS.md the falsifiable gate checklist +``` + +The bound plugin lives under `src/plugins//` and the skill under +`skills//` (registered through their own ecosystems); the persona owns +them logically via the manifest, it does not physically contain them. diff --git a/personas/product-manager/CHECKS.md b/personas/product-manager/CHECKS.md new file mode 100644 index 0000000..65998e5 --- /dev/null +++ b/personas/product-manager/CHECKS.md @@ -0,0 +1,8 @@ +# Product Manager Gate Checks (falsifiable) + +- [ ] Statement is an OPPORTUNITY, not a solution in disguise (more than one way to address it). +- [ ] Customer evidence exists; no requirement rests on "they said they want X". +- [ ] VALUE risk addressed: will users choose it? +- [ ] VIABILITY risk addressed: can the business sell/support/fund/legally ship it? +- [ ] A single prioritized call is stated, with a rationale (not a feature list). +- [ ] Scope cut: what is explicitly NOT being built and why. diff --git a/personas/product-manager/LIFECYCLE.md b/personas/product-manager/LIFECYCLE.md new file mode 100644 index 0000000..6cbb8d5 --- /dev/null +++ b/personas/product-manager/LIFECYCLE.md @@ -0,0 +1,23 @@ +# Product Manager Persona Lifecycle + +## Engage when +- net-new feature, new product, build request, or scope decision (hard gate) +- tweak/bugfix/refactor (advisory only) + +## Gate steps +1. Frame: `product_manager_opportunity_vs_solution` - reject solutions-in-disguise. +2. Ground: confirm customer evidence; flag opinion-requirements (`product_manager_get_discovery_rules`). +3. Assess: `product_manager_get_four_risks` - especially value + viability. +4. Prioritize: `product_manager_score_rice` / strategy rules - state the one call. +5. Emit: `product_manager_resolve_product_decision` -> PASS | BLOCK | NEEDS-INPUT + rationale. + +## Verdicts +- PASS: value + viability addressed, prioritized call has a rationale -> hand to `hyper`. +- BLOCK: hard gate, a required risk unaddressed -> stop, report what is missing. +- NEEDS-INPUT: ambiguity -> ask the user; never silently pass. + +## Override +- User may explicitly say "skip PM" -> honour, log the override in the decision trail. + +## Handback +- Always return to `hyper` for routing and ship-gate. The persona never delivers. diff --git a/personas/product-manager/PROFILE.md b/personas/product-manager/PROFILE.md new file mode 100644 index 0000000..55aa258 --- /dev/null +++ b/personas/product-manager/PROFILE.md @@ -0,0 +1,37 @@ +--- +name: product-manager +kind: persona +auto_invoke_when: + - net-new feature + - new product + - build request + - scope decision +owns: + - value risk (will users choose it) + - viability risk (does it work for the business) +must_not_do: + - approve a net-new build without value + viability addressed + - ask customers what they want instead of eliciting past-behaviour stories + - let a solution masquerade as a validated problem +delegates_to: + - hyper +--- + +# Product Manager Persona + +## Mission + +Ground every build decision in a validated customer problem, make the prioritized +call, and own value + viability before design or engineering begins. + +## Voice + +Opinionated and evidence-backed. States a recommendation with a rationale, never +hedges with "here are five options". Cites the customer problem and the business +case. Says no to good ideas that are not the lever. + +## Authority + +- Owns the value and viability product risks (Cagan four-risks split). +- Engaged by `hyper` before specialists on net-new build/scope work. +- Hands back to `hyper` for routing, verification, and ship-gate. Never self-ships. diff --git a/scripts/audit/sources.ts b/scripts/audit/sources.ts index 15c4482..97f8e39 100644 --- a/scripts/audit/sources.ts +++ b/scripts/audit/sources.ts @@ -41,6 +41,6 @@ export const SOURCES: PluginSource[] = [ { plugin: "ui-ux", editorial: true, skip: false, skills: [], packages: [] }, { plugin: "designer", editorial: true, skip: false, skills: ["designer"], packages: [] }, { plugin: "optimizer", editorial: true, skip: false, skills: ["optimizer"], packages: [] }, - { plugin: "product-manager", editorial: true, skip: false, skills: [], packages: [] }, + { plugin: "product-manager", editorial: true, skip: false, skills: ["pm-gate"], packages: [] }, { plugin: "hyperstack", editorial: false, skip: true, skills: [], packages: [] }, ]; diff --git a/skills/INDEX.md b/skills/INDEX.md index e2aa616..c488852 100644 --- a/skills/INDEX.md +++ b/skills/INDEX.md @@ -25,6 +25,7 @@ Categories: | `lab` | Use when designing or revamping a frontend section or whole page and you want to explore real-React variants in an isola | | `optimizer` | Teaches runtime analysis - deriving Big-O straight from code - and how to derive a better algorithm by removing redundan | | `parallel-dispatch` | Use when facing 2+ independent tasks that can be investigated or executed without shared state or sequential dependencie | +| `pm-gate` | Use BEFORE any net-new feature, product, or scope decision - the product-manager persona gate. Grounds the build in a va | | `run-plan` | Use when you have an existing plan, spec, or task list to execute. Validates the plan for gaps and MCP accuracy before a | | `ship-gate` | Use before claiming any work is complete, fixed, or passing. Run the verification command and show output before making | | `subagent-ops` | Use when executing implementation plans with independent tasks. Dispatches one fresh subagent per task with two-stage re | diff --git a/skills/pm-gate/SKILL.md b/skills/pm-gate/SKILL.md new file mode 100644 index 0000000..297c709 --- /dev/null +++ b/skills/pm-gate/SKILL.md @@ -0,0 +1,47 @@ +--- +name: pm-gate +description: Use BEFORE any net-new feature, product, or scope decision - the product-manager persona gate. Grounds the build in a validated customer problem (opportunity-vs-solution, four risks, prioritized call) and emits PASS/BLOCK/NEEDS-INPUT before design or engineering. Advisory for tweaks; user can override. +category: core +--- + +# PM Gate + +The product-manager persona's gate. It owns the value and viability product risks +that nothing else in Hyperstack owns. It runs BEFORE designer/blueprint on net-new +work. + +## Iron Law + +``` +NO NET-NEW BUILD WITHOUT A PASSED PRODUCT DECISION +(value + viability addressed, prioritized call stated with a rationale) +``` + +## When + +| Request | Gate | +|---|---| +| net-new feature / product / scope decision | HARD - must PASS before design/build | +| tweak / bugfix / refactor | ADVISORY - emit brief, do not block | +| user says "skip PM" | OVERRIDE - honour, log it | + +## Steps (MCP-grounded - call the tools, do not reason from memory) + +1. `product_manager_opportunity_vs_solution(statement)` - reject a solution-in-disguise. +2. `product_manager_get_discovery_rules()` - confirm customer evidence; flag opinion-requirements. +3. `product_manager_get_four_risks()` - assess all four, especially VALUE and VIABILITY. +4. `product_manager_score_rice(...)` and `product_manager_get_strategy_rules()` - state the one call, cut scope. +5. `product_manager_resolve_product_decision(description, isNetNew)` - emit the verdict. + +## Verdict handling + +- PASS -> hand to `hyper`; proceed to designer/blueprint. +- BLOCK -> stop; report the unaddressed risk; do not route to build. +- NEEDS-INPUT -> ask the user the specific missing question. + +## Red flags (from the corpus) + +- "A customer said they want X" used as a requirement -> opinion, not evidence. +- Reasoning covers value but is silent on viability -> the named weak-PM failure. +- "Strategy" that is a long feature list rejecting nothing -> not a strategy. +- Treating a strategy/interpersonal/culture problem as an execution problem. From 2764324b2c4518caaf99b232a156e173afcd97e2 Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Sun, 21 Jun 2026 22:58:53 +0530 Subject: [PATCH 09/16] fix(product-manager): kill theater, make the gate real (validation pass) Secondary-pass validation (1 empirical battery + 2 independent red-team agents) found the 3 'logic' tools were theater: resolve_product_decision could never return PASS/BLOCK (hardcoded NEEDS-INPUT), and the regex classifiers were ~80% accurate, gameable, and confidently wrong - violating the ground-truth-not- judgment philosophy the rest of the stack follows. - resolve_product_decision: now takes value+viability assessments as input and returns a real PASS (both addressed) / BLOCK (net-new, missing) / ADVISORY (tweak) verdict. PASS is earned, not automatic. - opportunity_vs_solution + validate_job_statement: drop the regex verdicts; return the Torres / JTBD rubric for the agent to apply (new snippets/rubrics/). - persona.json: drop the dangling 'agent: product-manager' claim (no such agent exists); persona is engaged_by hyper, realized via the pm-gate skill. - LIFECYCLE: remove the unimplemented 'decision trail' overclaim. bun test 63 pass, tsc clean. The 6 framework tools were sound and untouched. --- personas/persona.schema.json | 5 +- personas/product-manager/LIFECYCLE.md | 14 ++-- personas/product-manager/persona.json | 4 +- skills/pm-gate/SKILL.md | 10 +-- src/personas/registry.ts | 3 +- src/plugins/product-manager/data.ts | 79 +++++++++++-------- .../snippets/rubrics/job-criteria.txt | 15 ++++ .../rubrics/opportunity-vs-solution.txt | 15 ++++ .../tools/opportunity-vs-solution.ts | 12 +-- .../tools/resolve-product-decision.ts | 36 ++++++--- .../tools/validate-job-statement.ts | 12 ++- tests/product-manager-behaviour.test.ts | 77 ++++++++++++------ 12 files changed, 181 insertions(+), 101 deletions(-) create mode 100644 src/plugins/product-manager/snippets/rubrics/job-criteria.txt create mode 100644 src/plugins/product-manager/snippets/rubrics/opportunity-vs-solution.txt diff --git a/personas/persona.schema.json b/personas/persona.schema.json index 7163052..131916f 100644 --- a/personas/persona.schema.json +++ b/personas/persona.schema.json @@ -9,12 +9,11 @@ "version": { "type": "string" }, "owns": { "type": "object", - "required": ["risks", "plugin", "skills", "agent"], + "required": ["risks", "plugin", "skills"], "properties": { "risks": { "type": "array", "items": { "type": "string" } }, "plugin": { "type": "string" }, - "skills": { "type": "array", "items": { "type": "string" } }, - "agent": { "type": "string" } + "skills": { "type": "array", "items": { "type": "string" } } } }, "engages_when": { "type": "array", "items": { "type": "string" } }, diff --git a/personas/product-manager/LIFECYCLE.md b/personas/product-manager/LIFECYCLE.md index 6cbb8d5..cfe4e68 100644 --- a/personas/product-manager/LIFECYCLE.md +++ b/personas/product-manager/LIFECYCLE.md @@ -5,19 +5,19 @@ - tweak/bugfix/refactor (advisory only) ## Gate steps -1. Frame: `product_manager_opportunity_vs_solution` - reject solutions-in-disguise. +1. Frame: `product_manager_opportunity_vs_solution` - apply the returned rubric to reframe a solution into the underlying need. 2. Ground: confirm customer evidence; flag opinion-requirements (`product_manager_get_discovery_rules`). -3. Assess: `product_manager_get_four_risks` - especially value + viability. +3. Assess: `product_manager_get_four_risks` - then write your value + viability assessments. 4. Prioritize: `product_manager_score_rice` / strategy rules - state the one call. -5. Emit: `product_manager_resolve_product_decision` -> PASS | BLOCK | NEEDS-INPUT + rationale. +5. Decide: `product_manager_resolve_product_decision(description, valueAssessment, viabilityAssessment, isNetNew)` - returns PASS only when you supplied both assessments. ## Verdicts -- PASS: value + viability addressed, prioritized call has a rationale -> hand to `hyper`. -- BLOCK: hard gate, a required risk unaddressed -> stop, report what is missing. -- NEEDS-INPUT: ambiguity -> ask the user; never silently pass. +- PASS: value + viability assessed -> hand to `hyper`; proceed to design/build. +- BLOCK: net-new build, a PM-owned risk unaddressed -> assess it and call again; do not route to build. +- ADVISORY: tweak with a gap -> proceed allowed, gap noted. ## Override -- User may explicitly say "skip PM" -> honour, log the override in the decision trail. +- User may explicitly say "skip PM" -> honour it and note the override in your response. ## Handback - Always return to `hyper` for routing and ship-gate. The persona never delivers. diff --git a/personas/product-manager/persona.json b/personas/product-manager/persona.json index 6397911..a856669 100644 --- a/personas/product-manager/persona.json +++ b/personas/product-manager/persona.json @@ -5,9 +5,9 @@ "owns": { "risks": ["value", "viability"], "plugin": "product-manager", - "skills": ["pm-gate"], - "agent": "product-manager" + "skills": ["pm-gate"] }, + "engaged_by": "hyper", "engages_when": ["net-new feature", "new product", "build request", "scope decision"], "gate_policy": { "net_new": "hard", "tweak": "advisory", "override": "user-explicit" } } diff --git a/skills/pm-gate/SKILL.md b/skills/pm-gate/SKILL.md index 297c709..ad51052 100644 --- a/skills/pm-gate/SKILL.md +++ b/skills/pm-gate/SKILL.md @@ -27,17 +27,17 @@ NO NET-NEW BUILD WITHOUT A PASSED PRODUCT DECISION ## Steps (MCP-grounded - call the tools, do not reason from memory) -1. `product_manager_opportunity_vs_solution(statement)` - reject a solution-in-disguise. +1. `product_manager_opportunity_vs_solution(statement)` - apply the returned rubric to reframe a solution into the underlying need. The tool gives the test, you make the call. 2. `product_manager_get_discovery_rules()` - confirm customer evidence; flag opinion-requirements. -3. `product_manager_get_four_risks()` - assess all four, especially VALUE and VIABILITY. +3. `product_manager_get_four_risks()` - then WRITE your value and viability assessments (cite the customer problem and the business case). 4. `product_manager_score_rice(...)` and `product_manager_get_strategy_rules()` - state the one call, cut scope. -5. `product_manager_resolve_product_decision(description, isNetNew)` - emit the verdict. +5. `product_manager_resolve_product_decision(description, valueAssessment, viabilityAssessment, isNetNew)` - returns PASS only if you supplied both assessments, else BLOCK (net-new) or ADVISORY (tweak). ## Verdict handling - PASS -> hand to `hyper`; proceed to designer/blueprint. -- BLOCK -> stop; report the unaddressed risk; do not route to build. -- NEEDS-INPUT -> ask the user the specific missing question. +- BLOCK -> assess the missing PM-owned risk and call again; do not route to build. +- ADVISORY -> tweak with a noted gap; proceed allowed. ## Red flags (from the corpus) diff --git a/src/personas/registry.ts b/src/personas/registry.ts index 0ab1317..57c3f76 100644 --- a/src/personas/registry.ts +++ b/src/personas/registry.ts @@ -10,7 +10,8 @@ export interface PersonaManifest { id: string; name: string; version: string; - owns: { risks: string[]; plugin: string; skills: string[]; agent: string }; + owns: { risks: string[]; plugin: string; skills: string[] }; + engaged_by?: string; engages_when: string[]; gate_policy: { net_new: "hard" | "advisory"; tweak: "hard" | "advisory"; override: string }; } diff --git a/src/plugins/product-manager/data.ts b/src/plugins/product-manager/data.ts index 8449c63..9a19d8b 100644 --- a/src/plugins/product-manager/data.ts +++ b/src/plugins/product-manager/data.ts @@ -1,11 +1,15 @@ -// Product-Manager persona - typed structure + decision LOGIC. +// Product-Manager persona - typed structure + the one piece of legitimate gate logic. // -// The prose corpus (risk questions, anti-pattern smells, discovery/strategy -// rules, JTBD framing) lives in snippets/*.txt and is loaded via the shared -// createSnippetLoader, identical to the other Hyperstack plugins. Only stable, -// source-cited PM craft (Cagan/SVPG, Torres, Intercom, Christensen, Doshi), -// transcribed from docs/research/2026-06-21-pm-craft-corpus.json. No -// project-specific product opinions live here. +// PHILOSOPHY (see optimizer/data.ts): tools provide GROUND-TRUTH the LLM should +// recall and apply, NOT classification/judgment the LLM does better itself. +// Earlier versions shipped regex "classifiers" (opportunity-vs-solution, +// job-statement validation) that emitted exclusive verdicts - those were brittle +// theater and were removed. The agent applies the rubrics (snippets/rubrics/*.txt); +// the only logic kept here is deterministic STRUCTURE checks, not judgment. +// +// Corpus prose lives in snippets/*.txt via createSnippetLoader, identical to the +// other plugins. Source-cited PM craft (Cagan/SVPG, Torres, Intercom, Christensen, +// Doshi), transcribed from docs/research/2026-06-21-pm-craft-corpus.json. import { snippet } from "./loader.js"; @@ -50,40 +54,47 @@ export const DISCOVERY_DOC: string = snippet("discovery/rules.txt"); export const STRATEGY_DOC: string = snippet("strategy/rules.txt"); export const JTBD_DOC: string = snippet("jtbd/jtbd.txt"); -// JTBD job-statement validator - the regex-checkable SUBSET (context, progress, -// not-a-single-solution). The full 7-point checklist (e.g. "not a trend", -// "appropriate abstraction") is not mechanically testable and is a Phase 2 item. -export interface JobCheck { id: string; test: (s: string) => boolean; failHint: string; } +// Rubrics the AGENT applies (ground truth, not a verdict the tool computes). +export const OPPORTUNITY_RUBRIC_DOC: string = snippet("rubrics/opportunity-vs-solution.txt"); +export const JOB_CRITERIA_DOC: string = snippet("rubrics/job-criteria.txt"); + +// --- The one legitimate piece of logic: a deterministic STRUCTURE gate. --- +// This does NOT judge whether the value/viability reasoning is good (that is the +// agent's job, supplied as input). It enforces the discipline: a net-new build +// cannot PASS unless the PM-owned risks (value AND viability) were actually +// addressed. A presence/substance check, not a judgment call. + +export interface DecisionInput { + description: string; + valueAssessment?: string; + viabilityAssessment?: string; + isNetNew?: boolean; +} -export const JOB_CHECKS: JobCheck[] = [ - { id: "has-context", test: (s) => /\b(when|while|after|before|during|because)\b/i.test(s), failHint: "missing context (when/while/because ...)" }, - { id: "not-single-solution", test: (s) => !/\b(button|toggle|page|dropdown|API|endpoint|screen)\b/i.test(s), failHint: "names a specific solution, not a job" }, - { id: "expresses-progress", test: (s) => /\b(so that|in order to|to|need|want to)\b/i.test(s), failHint: "does not express the progress sought" }, -]; +export interface Decision { + verdict: "PASS" | "BLOCK" | "ADVISORY"; + gate: "HARD" | "ADVISORY"; + missing: string[]; +} -export interface JobValidation { valid: boolean; passed: string[]; failed: { id: string; failHint: string }[]; } +const MIN_ASSESSMENT_CHARS = 20; -export function validateJobStatement(statement: string): JobValidation { - const passed: string[] = []; - const failed: { id: string; failHint: string }[] = []; - for (const c of JOB_CHECKS) { - if (c.test(statement)) passed.push(c.id); - else failed.push({ id: c.id, failHint: c.failHint }); - } - return { valid: failed.length === 0, passed, failed }; +function addressed(text?: string): boolean { + return typeof text === "string" && text.trim().length >= MIN_ASSESSMENT_CHARS; } -export interface OppClassification { isOpportunity: boolean; reason: string; } +export function computeDecision(input: DecisionInput): Decision { + const gate: Decision["gate"] = input.isNetNew === false ? "ADVISORY" : "HARD"; + const missing: string[] = []; + if (!addressed(input.valueAssessment)) missing.push("value"); + if (!addressed(input.viabilityAssessment)) missing.push("viability"); -// Torres test: "is there more than one way to address this?" If a statement -// names a single concrete mechanism, it is a solution disguised as a problem. -const SOLUTION_MARKERS = /\b(add|build|create|implement|use|toggle|button|dropdown|dark mode|integrate)\b/i; + let verdict: Decision["verdict"]; + if (missing.length === 0) verdict = "PASS"; + else if (gate === "ADVISORY") verdict = "ADVISORY"; + else verdict = "BLOCK"; -export function classifyOpportunityVsSolution(statement: string): OppClassification { - if (SOLUTION_MARKERS.test(statement)) { - return { isOpportunity: false, reason: "Names a single concrete mechanism. Restate as the underlying need it serves (there should be more than one way to address it)." }; - } - return { isOpportunity: true, reason: "Stated as a need/pain with more than one possible solution." }; + return { verdict, gate, missing }; } export interface RiceInput { reach: number; impact: number; confidence: number; effort: number; } diff --git a/src/plugins/product-manager/snippets/rubrics/job-criteria.txt b/src/plugins/product-manager/snippets/rubrics/job-criteria.txt new file mode 100644 index 0000000..d95d7d2 --- /dev/null +++ b/src/plugins/product-manager/snippets/rubrics/job-criteria.txt @@ -0,0 +1,15 @@ +Judge a JTBD job statement against these 7 criteria yourself (Christensen / Ulwick). This is the rubric, not a pass/fail machine. + +A valid job statement: +1. Is NOT a trend or fad. +2. Is NOT a vague high-level need ("be productive"). +3. Is NOT tied to one product class or technology. +4. Does NOT imply a single solution. +5. States the PROGRESS the person wants to make. +6. Includes the CONTEXT - the circumstances, external and emotional. +7. Sits at an appropriate level of abstraction (not too broad, not a feature). + +Good: "When I'm commuting and bored, help me feel productive without needing focus." +Weak: "I want a faster app." (no context, no progress, names a property not a job) + +Form: "When [situation], I want to [progress], so I can [outcome]." \ No newline at end of file diff --git a/src/plugins/product-manager/snippets/rubrics/opportunity-vs-solution.txt b/src/plugins/product-manager/snippets/rubrics/opportunity-vs-solution.txt new file mode 100644 index 0000000..1bc5433 --- /dev/null +++ b/src/plugins/product-manager/snippets/rubrics/opportunity-vs-solution.txt @@ -0,0 +1,15 @@ +Apply this test yourself to any build request (Torres). This is the rubric, not a verdict. + +ASK: "Is there more than one way to address this?" +- Only ONE way exists -> it is a SOLUTION in disguise. Reframe to the underlying need. +- Several ways exist -> it is an OPPORTUNITY. Keep it. + +Also check: does the statement name a MECHANISM (a verb of construction - add / build / integrate / implement - or a specific UI element) or a desired OUTCOME / need? Run the 5 Whys to reach the need beneath a proposed solution. + +Reframe examples (solution -> opportunity): +- "add a dark mode toggle" -> "reduce eye strain when using the app in low light" +- "build a Slack integration" -> "let users act on alerts without leaving their workflow" +- "add a CSV export button" -> "let users get their data into their own analysis tools" +- "implement SSO" -> "let enterprise users sign in without managing another password" + +Note: "add", "build", "integrate" are not always solutions - "build trust with new users" is a need. Judge by structure (mechanism vs outcome), not by keywords. You make the call. \ No newline at end of file diff --git a/src/plugins/product-manager/tools/opportunity-vs-solution.ts b/src/plugins/product-manager/tools/opportunity-vs-solution.ts index 4dd7c83..32f8f80 100644 --- a/src/plugins/product-manager/tools/opportunity-vs-solution.ts +++ b/src/plugins/product-manager/tools/opportunity-vs-solution.ts @@ -1,16 +1,16 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; -import { classifyOpportunityVsSolution } from "../data.js"; +import { OPPORTUNITY_RUBRIC_DOC } from "../data.js"; export function register(server: McpServer): void { server.tool( "product_manager_opportunity_vs_solution", - "Torres test: is a statement a real opportunity (a need with more than one way to address it) or a solution in disguise? Rejects premature solution-jumping.", - { statement: z.string().describe("The problem/feature statement to classify.") }, + "Returns the Torres opportunity-vs-solution rubric (the test + reframe examples) for YOU to apply - it does not classify for you. Use to reframe a solution-shaped request into the underlying need.", + { statement: z.string().optional().describe("Optional: the statement you want to apply the rubric to.") }, async ({ statement }) => { - const r = classifyOpportunityVsSolution(statement); - const verdict = r.isOpportunity ? "OPPORTUNITY" : "SOLUTION (reframe needed)"; - return { content: [{ type: "text" as const, text: `# ${verdict}\n\n**Statement:** ${statement}\n\n${r.reason}\n` }] }; + let text = `# Opportunity vs Solution (rubric)\n\n${OPPORTUNITY_RUBRIC_DOC}\n`; + if (statement) text += `\n---\nApply the test to: "${statement}"\n`; + return { content: [{ type: "text" as const, text }] }; }, ); } diff --git a/src/plugins/product-manager/tools/resolve-product-decision.ts b/src/plugins/product-manager/tools/resolve-product-decision.ts index 464c601..a6c878b 100644 --- a/src/plugins/product-manager/tools/resolve-product-decision.ts +++ b/src/plugins/product-manager/tools/resolve-product-decision.ts @@ -1,27 +1,37 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; -import { FOUR_RISKS, classifyOpportunityVsSolution } from "../data.js"; +import { computeDecision, FOUR_RISKS } from "../data.js"; export function register(server: McpServer): void { server.tool( "product_manager_resolve_product_decision", - "The PM gate engine. Takes a build/feature description and returns a structured product decision: opportunity framing, four-risks assessment (esp value+viability), and a PASS/BLOCK/NEEDS-INPUT verdict the gate enforces.", + "The PM gate. Supply YOUR value and viability assessments for the proposal; returns a real verdict - PASS (both addressed), BLOCK (net-new build with a PM-owned risk unaddressed), or ADVISORY (tweak). PASS is earned by actually assessing value AND viability, not automatic. The judgment is yours; this enforces that you made it.", { description: z.string().describe("What is being proposed to build."), + valueAssessment: z.string().optional().describe("Your assessment of VALUE risk: will users choose it? Cite the customer problem/evidence."), + viabilityAssessment: z.string().optional().describe("Your assessment of VIABILITY risk: can the business sell/support/fund/legally ship it?"), isNetNew: z.boolean().optional().describe("True for a net-new feature/product (hard gate); false for a tweak (advisory)."), }, - async ({ description, isNetNew }) => { - const opp = classifyOpportunityVsSolution(description); - let text = `# Product Decision\n\n**Proposal:** ${description}\n\n`; - text += `## 1. Opportunity framing\n${opp.isOpportunity ? "Framed as an opportunity." : "SOLUTION in disguise - reframe to the underlying need."} ${opp.reason}\n\n`; - text += `## 2. Four-risks assessment (fill before proceeding)\n`; - for (const r of FOUR_RISKS) text += `- **${r.id}** (${r.owner}): ${r.question} -> _unassessed_\n`; - text += `\n## 3. Verdict\n`; - const gate = isNetNew === false ? "ADVISORY" : "HARD"; - if (!opp.isOpportunity) { - text += `NEEDS-INPUT (${gate}): restate as an opportunity, then assess value + viability before build.\n`; + async ({ description, valueAssessment, viabilityAssessment, isNetNew }) => { + const d = computeDecision({ description, valueAssessment, viabilityAssessment, isNetNew }); + + let text = `# Product Decision: ${d.verdict}\n\n**Proposal:** ${description}\n\n`; + text += `## PM-owned risk assessments\n`; + text += `- **value:** ${valueAssessment?.trim() || "_not provided_"}\n`; + text += `- **viability:** ${viabilityAssessment?.trim() || "_not provided_"}\n\n`; + text += `## Verdict: ${d.verdict} (${d.gate} gate)\n`; + + if (d.verdict === "PASS") { + text += `Value and viability are addressed. Hand back to \`hyper\`; proceed to design/build.\n`; + } else if (d.verdict === "BLOCK") { + text += `Unaddressed PM-owned risk(s): **${d.missing.join(", ")}**. A net-new build cannot proceed until value AND viability are addressed. Assess the missing risk(s) and call this tool again.\n`; } else { - text += `NEEDS-INPUT (${gate}): assess all four risks - especially value and viability - and state the prioritized call with a rationale. PASS only when value+viability are addressed.\n`; + text += `Tweak / non-net-new: unaddressed **${d.missing.join(", ")}** noted as advisory. Proceed allowed, but the gap is on the record.\n`; + } + + text += `\n## The other two risks (not PM-owned, confirm before ship)\n`; + for (const r of FOUR_RISKS.filter((r) => r.owner !== "product-manager")) { + text += `- ${r.id} (${r.owner}): ${r.question}\n`; } return { content: [{ type: "text" as const, text }] }; }, diff --git a/src/plugins/product-manager/tools/validate-job-statement.ts b/src/plugins/product-manager/tools/validate-job-statement.ts index c1ae522..b663d5b 100644 --- a/src/plugins/product-manager/tools/validate-job-statement.ts +++ b/src/plugins/product-manager/tools/validate-job-statement.ts @@ -1,17 +1,15 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; -import { validateJobStatement } from "../data.js"; +import { JOB_CRITERIA_DOC } from "../data.js"; export function register(server: McpServer): void { server.tool( "product_manager_validate_job_statement", - "Validate a JTBD job statement against the falsifiable checklist (has context, expresses progress, not a single solution). Returns pass/fail per check.", - { statement: z.string().describe("The job statement to validate.") }, + "Returns the 7-point JTBD job-statement criteria (Christensen/Ulwick) for YOU to judge a statement against - it does not pass/fail for you. Use to sharpen a vague job into one with context + progress.", + { statement: z.string().optional().describe("Optional: the job statement you want to judge against the criteria.") }, async ({ statement }) => { - const r = validateJobStatement(statement); - let text = `# Job statement: ${r.valid ? "VALID" : "INVALID"}\n\n**Statement:** ${statement}\n\n`; - text += `Passed: ${r.passed.join(", ") || "none"}\n\n`; - if (r.failed.length) text += `Failed:\n${r.failed.map((f) => `- ${f.id}: ${f.failHint}`).join("\n")}\n`; + let text = `# JTBD Job-Statement Criteria (rubric)\n\n${JOB_CRITERIA_DOC}\n`; + if (statement) text += `\n---\nJudge this statement against the 7 criteria: "${statement}"\n`; return { content: [{ type: "text" as const, text }] }; }, ); diff --git a/tests/product-manager-behaviour.test.ts b/tests/product-manager-behaviour.test.ts index cf5ce92..f9b39e5 100644 --- a/tests/product-manager-behaviour.test.ts +++ b/tests/product-manager-behaviour.test.ts @@ -1,48 +1,59 @@ import { test, expect } from "bun:test"; import { FOUR_RISKS, - classifyOpportunityVsSolution, - validateJobStatement, - scoreRice, PM_OWNED_RISKS, + computeDecision, + scoreRice, } from "../src/plugins/product-manager/data.ts"; import { captureTool, extractTextContent } from "./helpers.ts"; import { register as getFourRisks } from "../src/plugins/product-manager/tools/get-four-risks.ts"; import { register as getAntiPatterns } from "../src/plugins/product-manager/tools/get-anti-patterns.ts"; import { register as oppVsSol } from "../src/plugins/product-manager/tools/opportunity-vs-solution.ts"; +import { register as validateJob } from "../src/plugins/product-manager/tools/validate-job-statement.ts"; import { register as scoreRiceTool } from "../src/plugins/product-manager/tools/score-rice.ts"; import { register as resolveDecision } from "../src/plugins/product-manager/tools/resolve-product-decision.ts"; +// --- data layer --- test("four risks are exactly the canonical four", () => { - expect(FOUR_RISKS.map((r) => r.id).sort()).toEqual( - ["feasibility", "usability", "value", "viability"], - ); + expect(FOUR_RISKS.map((r) => r.id).sort()).toEqual(["feasibility", "usability", "value", "viability"]); }); test("PM owns exactly value and viability", () => { expect([...PM_OWNED_RISKS].sort()).toEqual(["value", "viability"]); }); -test("single-path statement is classified as a solution, not an opportunity", () => { - const r = classifyOpportunityVsSolution("add a dark mode toggle"); - expect(r.isOpportunity).toBe(false); +test("rice score is (reach*impact*confidence)/effort", () => { + expect(scoreRice({ reach: 100, impact: 2, confidence: 0.8, effort: 4 }).score).toBeCloseTo(40); }); -test("multi-path statement is classified as an opportunity", () => { - const r = classifyOpportunityVsSolution("users cannot find their past orders"); - expect(r.isOpportunity).toBe(true); +// --- the real gate: PASS is earned, BLOCK on missing, ADVISORY for tweaks --- +test("computeDecision PASSes when value and viability are both assessed", () => { + const d = computeDecision({ + description: "x", + valueAssessment: "users repeatedly ask for this in interviews, strong pull", + viabilityAssessment: "no legal/support cost, fits the paid tier", + isNetNew: true, + }); + expect(d.verdict).toBe("PASS"); + expect(d.missing).toEqual([]); }); -test("job statement missing context fails validation", () => { - const r = validateJobStatement("I want a faster app"); - expect(r.valid).toBe(false); - expect(r.failed.length).toBeGreaterThan(0); +test("computeDecision BLOCKs a net-new build with viability unaddressed", () => { + const d = computeDecision({ + description: "x", + valueAssessment: "users repeatedly ask for this, strong evidence of pull", + isNetNew: true, + }); + expect(d.verdict).toBe("BLOCK"); + expect(d.missing).toContain("viability"); }); -test("rice score is (reach*impact*confidence)/effort", () => { - expect(scoreRice({ reach: 100, impact: 2, confidence: 0.8, effort: 4 }).score).toBeCloseTo(40); +test("computeDecision is ADVISORY (not BLOCK) for a tweak", () => { + const d = computeDecision({ description: "x", isNetNew: false }); + expect(d.verdict).toBe("ADVISORY"); }); +// --- tools --- test("get_four_risks names value and viability as PM-owned", async () => { const tool = captureTool(getFourRisks); expect(tool.name).toBe("product_manager_get_four_risks"); @@ -58,11 +69,19 @@ test("get_anti_patterns includes the feature-factory smell", async () => { expect(text).toMatch(/feature-factory/); }); -test("opportunity_vs_solution flags a solution-shaped statement", async () => { +test("opportunity_vs_solution returns the rubric for the agent to apply, not a verdict", async () => { const tool = captureTool(oppVsSol); expect(tool.name).toBe("product_manager_opportunity_vs_solution"); const text = extractTextContent(await tool.invoke({ statement: "add a dark mode toggle" })); - expect(text).toMatch(/solution/i); + expect(text).toMatch(/more than one way/i); + expect(text).toMatch(/rubric/i); +}); + +test("validate_job_statement returns the criteria rubric", async () => { + const tool = captureTool(validateJob); + const text = extractTextContent(await tool.invoke({ statement: "I want a faster app" })); + expect(text).toMatch(/criteria/i); + expect(text).toMatch(/context/i); }); test("score_rice renders the computed score", async () => { @@ -71,10 +90,22 @@ test("score_rice renders the computed score", async () => { expect(text).toMatch(/40/); }); -test("resolve_product_decision emits a verdict and the four risks", async () => { +test("resolve_product_decision BLOCKs a net-new build with no assessments", async () => { const tool = captureTool(resolveDecision); expect(tool.name).toBe("product_manager_resolve_product_decision"); - const text = extractTextContent(await tool.invoke({ description: "add a dark mode toggle" })); - expect(text).toMatch(/VERDICT|NEEDS-INPUT|BLOCK|PASS/); + const text = extractTextContent(await tool.invoke({ description: "add CSV export", isNetNew: true })); + expect(text).toMatch(/BLOCK/); + expect(text).toMatch(/value/i); expect(text).toMatch(/viability/i); }); + +test("resolve_product_decision PASSes when value and viability are supplied", async () => { + const tool = captureTool(resolveDecision); + const text = extractTextContent(await tool.invoke({ + description: "add CSV export", + valueAssessment: "top request from power users who export to Excel weekly", + viabilityAssessment: "trivial to support, no legal exposure, fits all tiers", + isNetNew: true, + })); + expect(text).toMatch(/PASS/); +}); From e711139a9cc3702013b517b78dd966dc89fe77f8 Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Sun, 21 Jun 2026 22:59:49 +0530 Subject: [PATCH 10/16] docs(persona): record validation-pass corrections in the spec --- .../specs/2026-06-21-pm-persona-design.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/superpowers/specs/2026-06-21-pm-persona-design.md b/docs/superpowers/specs/2026-06-21-pm-persona-design.md index 91f8281..58e0848 100644 --- a/docs/superpowers/specs/2026-06-21-pm-persona-design.md +++ b/docs/superpowers/specs/2026-06-21-pm-persona-design.md @@ -191,4 +191,16 @@ Mirror existing `*-behaviour.test.ts`: ## Future personas (why the vertical, not a one-off) -The vertical is justified only if more personas follow. Candidates that fit the same "judgment lens that owns a risk" shape: a security persona (owns risk/threat), a data/analytics persona (owns measurement), an SRE persona (owns reliability). Each = manifest + bound plugin/skill/agent. The registry, schema, and bootstrap layer built here are shared infrastructure. +The vertical is justified only if more personas follow. Candidates that fit the same "judgment lens that owns a risk" shape: a security persona (owns risk/threat), a data/analytics persona (owns measurement), an SRE persona (owns reliability). Each = manifest + bound plugin/skill. The registry, schema, and bootstrap layer built here are shared infrastructure. + +## Validation pass (post-implementation correction) + +A secondary validation (1 empirical accuracy battery + 2 independent adversarial red-team agents) found that 3 of the 9 tools as first built were theater, and corrected them. This supersedes the earlier descriptions of those tools above: + +| Tool | As-built flaw | Corrected to | +|---|---|---| +| `resolve_product_decision` | hardcoded `NEEDS-INPUT`, never PASS/BLOCK - the "gate engine" could not gate | takes value+viability assessments as input; deterministic verdict PASS (both addressed) / BLOCK (net-new, missing) / ADVISORY (tweak). PASS earned, not automatic. | +| `opportunity_vs_solution` | ~80% regex classifier emitting an exclusive verdict (the LLM does this better) | returns the Torres rubric + reframe examples for the agent to apply (ground-truth, no verdict) | +| `validate_job_statement` | 3-regex pseudo-validator | returns the 7-point JTBD criteria for the agent to judge against | + +Also corrected: `persona.json` dropped the dangling `agent: product-manager` claim (no such agent exists; the persona is `engaged_by` hyper and realized via the `pm-gate` skill), and LIFECYCLE dropped an unimplemented "decision trail" claim. The 6 framework tools (`get_*` + `score_rice`) were sound and unchanged. Verdict labels are now PASS/BLOCK/ADVISORY (not NEEDS-INPUT). A runtime write-blocking hook was deliberately declined: all Hyperstack skills are prose-enforced via the bootstrap, and the diagnosed problem was decision quality, not enforcement mechanics. From ff322e497df54663f9246b109b945aa1789892f5 Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Mon, 22 Jun 2026 01:33:34 +0530 Subject: [PATCH 11/16] feat(personas): marketing persona (capability mode) - 2nd persona Generalizes the persona vertical from gate-only to gate OR capability. Adds a marketing persona (capability) that does product marketing for any brand: - marketing MCP plugin: 12 ground-truth tools over a 19-snippet corpus (positioning/Dunford, messaging/StoryBrand+value-prop, copy formulas+Schwartz awareness+Cialdini+headlines, brand archetypes+Sharp+category-design, GTM motions+Traction-Bullseye+AARRR+loops, anti-patterns) + a real brief assembler. - skills/marketing: the position->message->copy->voice->GTM workflow + on-demand mode. - persona manifest (mode: capability), contracts, bootstrap Layer 4 generalized. Tools = ground truth only (PM validation lesson baked in - no regex theater). 15th plugin. bun test 72 pass, tsc clean, bootstrap regenerated. --- .../runtime-context/hyperstack.bootstrap.md | 3 +- personas/README.md | 19 ++++-- personas/marketing/LIFECYCLE.md | 23 +++++++ personas/marketing/PROFILE.md | 42 ++++++++++++ personas/marketing/persona.json | 20 ++++++ personas/persona.schema.json | 12 ++-- personas/product-manager/persona.json | 1 + scripts/audit/sources.ts | 1 + skills/INDEX.md | 1 + skills/hyperstack/SKILL.md | 13 ++-- skills/marketing/SKILL.md | 44 +++++++++++++ src/index.ts | 2 + src/internal/context-compiler.ts | 2 +- src/personas/registry.ts | 11 ++-- src/plugins/marketing/data.ts | 64 +++++++++++++++++++ src/plugins/marketing/index.ts | 34 ++++++++++ src/plugins/marketing/loader.ts | 3 + .../snippets/anti-patterns/marketing.txt | 12 ++++ .../marketing/snippets/brand/archetypes.txt | 18 ++++++ .../snippets/brand/category-design.txt | 16 +++++ src/plugins/marketing/snippets/brand/laws.txt | 13 ++++ .../marketing/snippets/brand/voice-tone.txt | 18 ++++++ .../snippets/copywriting/awareness-stages.txt | 12 ++++ .../snippets/copywriting/formulas.txt | 23 +++++++ .../snippets/copywriting/headlines.txt | 19 ++++++ .../snippets/copywriting/landing-anatomy.txt | 13 ++++ .../snippets/copywriting/persuasion.txt | 11 ++++ .../marketing/snippets/gtm/channels.txt | 13 ++++ .../marketing/snippets/gtm/growth-model.txt | 17 +++++ src/plugins/marketing/snippets/gtm/launch.txt | 13 ++++ .../marketing/snippets/gtm/motions.txt | 13 ++++ .../snippets/messaging/narrative.txt | 16 +++++ .../snippets/messaging/storybrand.txt | 13 ++++ .../snippets/messaging/value-prop-canvas.txt | 15 +++++ .../snippets/positioning/dunford.txt | 20 ++++++ .../marketing/snippets/positioning/styles.txt | 7 ++ src/plugins/marketing/tools/brief.ts | 24 +++++++ .../marketing/tools/get-anti-patterns.ts | 11 ++++ .../marketing/tools/get-awareness-stages.ts | 11 ++++ .../marketing/tools/get-brand-strategy.ts | 11 ++++ src/plugins/marketing/tools/get-channels.ts | 11 ++++ .../tools/get-copywriting-formulas.ts | 11 ++++ .../marketing/tools/get-growth-model.ts | 11 ++++ src/plugins/marketing/tools/get-gtm.ts | 11 ++++ src/plugins/marketing/tools/get-messaging.ts | 11 ++++ src/plugins/marketing/tools/get-persuasion.ts | 11 ++++ .../marketing/tools/get-positioning.ts | 11 ++++ src/plugins/marketing/tools/get-voice.ts | 11 ++++ tests/marketing-behaviour.test.ts | 54 ++++++++++++++++ tests/persona-registry-behaviour.test.ts | 8 +++ tests/plugin-registry-behaviour.test.ts | 11 +++- 51 files changed, 771 insertions(+), 24 deletions(-) create mode 100644 personas/marketing/LIFECYCLE.md create mode 100644 personas/marketing/PROFILE.md create mode 100644 personas/marketing/persona.json create mode 100644 skills/marketing/SKILL.md create mode 100644 src/plugins/marketing/data.ts create mode 100644 src/plugins/marketing/index.ts create mode 100644 src/plugins/marketing/loader.ts create mode 100644 src/plugins/marketing/snippets/anti-patterns/marketing.txt create mode 100644 src/plugins/marketing/snippets/brand/archetypes.txt create mode 100644 src/plugins/marketing/snippets/brand/category-design.txt create mode 100644 src/plugins/marketing/snippets/brand/laws.txt create mode 100644 src/plugins/marketing/snippets/brand/voice-tone.txt create mode 100644 src/plugins/marketing/snippets/copywriting/awareness-stages.txt create mode 100644 src/plugins/marketing/snippets/copywriting/formulas.txt create mode 100644 src/plugins/marketing/snippets/copywriting/headlines.txt create mode 100644 src/plugins/marketing/snippets/copywriting/landing-anatomy.txt create mode 100644 src/plugins/marketing/snippets/copywriting/persuasion.txt create mode 100644 src/plugins/marketing/snippets/gtm/channels.txt create mode 100644 src/plugins/marketing/snippets/gtm/growth-model.txt create mode 100644 src/plugins/marketing/snippets/gtm/launch.txt create mode 100644 src/plugins/marketing/snippets/gtm/motions.txt create mode 100644 src/plugins/marketing/snippets/messaging/narrative.txt create mode 100644 src/plugins/marketing/snippets/messaging/storybrand.txt create mode 100644 src/plugins/marketing/snippets/messaging/value-prop-canvas.txt create mode 100644 src/plugins/marketing/snippets/positioning/dunford.txt create mode 100644 src/plugins/marketing/snippets/positioning/styles.txt create mode 100644 src/plugins/marketing/tools/brief.ts create mode 100644 src/plugins/marketing/tools/get-anti-patterns.ts create mode 100644 src/plugins/marketing/tools/get-awareness-stages.ts create mode 100644 src/plugins/marketing/tools/get-brand-strategy.ts create mode 100644 src/plugins/marketing/tools/get-channels.ts create mode 100644 src/plugins/marketing/tools/get-copywriting-formulas.ts create mode 100644 src/plugins/marketing/tools/get-growth-model.ts create mode 100644 src/plugins/marketing/tools/get-gtm.ts create mode 100644 src/plugins/marketing/tools/get-messaging.ts create mode 100644 src/plugins/marketing/tools/get-persuasion.ts create mode 100644 src/plugins/marketing/tools/get-positioning.ts create mode 100644 src/plugins/marketing/tools/get-voice.ts create mode 100644 tests/marketing-behaviour.test.ts diff --git a/generated/runtime-context/hyperstack.bootstrap.md b/generated/runtime-context/hyperstack.bootstrap.md index a5310a3..787c450 100644 --- a/generated/runtime-context/hyperstack.bootstrap.md +++ b/generated/runtime-context/hyperstack.bootstrap.md @@ -79,8 +79,9 @@ Hyperstack is a **Three-Layer Ecosystem**: - `website-builder` - first specialist for website-facing design and ## Personas -- Personas are internal judgment lenses that own and gate a decision class. +- 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`. ## Routing Summary - Every request enters through `hyper` diff --git a/personas/README.md b/personas/README.md index bc2bb4c..fc10c89 100644 --- a/personas/README.md +++ b/personas/README.md @@ -1,14 +1,19 @@ # Personas (Layer 4) -Personas are judgment lenses that own and gate a class of decision before -execution. Each persona binds an MCP plugin (ground-truth), one or more skills -(process + gate), and a role identity via `persona.json`. The persona registry -(`src/personas/registry.ts`) loads manifests; the bootstrap compiles a Personas -layer so `hyper` knows which personas exist and when they engage. +Personas are domain-expert lenses that `hyper` engages for a domain. Each persona +binds an MCP plugin (ground-truth), one or more skills (process), and a role +identity via `persona.json`. Two modes: -| Persona | Owns | First | +- **gate** - owns a risk and blocks before execution (e.g. `product-manager`). +- **capability** - produces domain output (e.g. `marketing`). + +The persona registry (`src/personas/registry.ts`) loads manifests; the bootstrap +compiles a Personas layer so `hyper` knows which personas exist and when they engage. + +| Persona | Mode | Owns | |---|---|---| -| `product-manager` | value + viability product risk | yes | +| `product-manager` | gate | value + viability product risk | +| `marketing` | capability | positioning, messaging, copy, brand, GTM | ## Anatomy of a persona diff --git a/personas/marketing/LIFECYCLE.md b/personas/marketing/LIFECYCLE.md new file mode 100644 index 0000000..fc8feee --- /dev/null +++ b/personas/marketing/LIFECYCLE.md @@ -0,0 +1,23 @@ +# Product Marketer Persona Lifecycle + +## Engage when +- positioning, messaging, copy, brand voice, GTM/growth, or "marketing words for X" requests + +## Full workflow (new brand, in order) +1. Intake: who is it for, what does it do, what are the competitive alternatives, what is unique. +2. Position: `marketing_get_positioning` - Dunford's 5 components in sequence (competitive alternatives -> unique attributes -> value -> target segment -> market category). +3. Message: `marketing_get_messaging` - value-proposition canvas (jobs/pains/gains) + StoryBrand SB7 + the strategic narrative; build a message hierarchy. +4. Awareness stage: `marketing_get_awareness_stages` - locate the market on Schwartz's 5 stages; the more aware, the less you need to say. +5. Write: `marketing_get_copywriting_formulas` + `marketing_get_persuasion` - choose a formula (PAS/AIDA/BAB...), write headline + body + CTA, apply Cialdini triggers. +6. Brand voice: `marketing_get_voice` - choose an archetype, set the tone dimensions, apply consistently. +7. GTM: `marketing_get_gtm` (motion), `marketing_get_channels` (19 channels + Bullseye), `marketing_get_growth_model` (AARRR + the growth loop). +8. Check: `marketing_get_anti_patterns` - no feature-dump, we-we copy, vague unproven claims. + +## On-demand mode ("give me marketing words for X") +- Skip to: awareness stage -> formula -> voice -> produce. Still grounded in the frameworks, not vibes. + +## Orchestrator +- `marketing_brief(brand, ...)` assembles the relevant frameworks into a structured brief for a specific brand. + +## Handback +- Return to `hyper` for review and delivery. The marketer produces output; it does not ship or gate. diff --git a/personas/marketing/PROFILE.md b/personas/marketing/PROFILE.md new file mode 100644 index 0000000..9689a7a --- /dev/null +++ b/personas/marketing/PROFILE.md @@ -0,0 +1,42 @@ +--- +name: marketing +kind: persona +mode: capability +auto_invoke_when: + - position a product or brand + - write marketing copy or words + - go-to-market or launch + - messaging or brand voice + - growth or channel strategy +owns: + - positioning, messaging, copywriting, GTM, brand +delegates_to: + - hyper +must_not_do: + - write copy before the positioning is decided + - feature-dump (list features instead of customer value) + - ship vague claims with no proof + - copy a template without adapting to the brand's category and audience +--- + +# Product Marketer Persona + +## Mission + +Do product marketing for any brand: position it, find the message, write the words, +choose the go-to-market. Every output traces to a named framework, not vibes. + +## Voice + +A senior product marketer. Customer-language first (their words, mined from how they +actually talk - not the company's jargon). Specific over clever. Differentiated, not +generic. Leads with the customer's problem and the value it unlocks, never a feature +list. Takes a point of view instead of hedging. + +## Authority + +- Produces positioning, messaging, copy, brand voice, and GTM/growth plans. +- Pulls ground-truth frameworks (Dunford, Schwartz, Cialdini, Byron Sharp, Traction, + Reforge, StoryBrand...) via the marketing MCP tools and applies them to the specific + brand - the tools supply the canon, the marketer makes the call. +- Hands back to `hyper` for review and delivery. Generative, not a gate - it does not block. diff --git a/personas/marketing/persona.json b/personas/marketing/persona.json new file mode 100644 index 0000000..924e92c --- /dev/null +++ b/personas/marketing/persona.json @@ -0,0 +1,20 @@ +{ + "id": "marketing", + "name": "Product Marketer", + "version": "0.1.0", + "mode": "capability", + "owns": { + "capabilities": ["positioning", "messaging", "copywriting", "gtm", "brand"], + "plugin": "marketing", + "skills": ["marketing"] + }, + "engaged_by": "hyper", + "engages_when": [ + "position a product or brand", + "write marketing copy or words", + "go-to-market or launch", + "messaging or brand voice", + "growth or channel strategy", + "marketing recommendation for an app or website" + ] +} diff --git a/personas/persona.schema.json b/personas/persona.schema.json index 131916f..b612153 100644 --- a/personas/persona.schema.json +++ b/personas/persona.schema.json @@ -2,23 +2,27 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Hyperstack Persona Manifest", "type": "object", - "required": ["id", "name", "version", "owns", "engages_when", "gate_policy"], + "required": ["id", "name", "version", "owns", "engages_when"], "properties": { "id": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" }, "name": { "type": "string" }, "version": { "type": "string" }, + "mode": { "enum": ["gate", "capability"], "description": "gate owns a risk and blocks; capability produces domain output" }, + "engaged_by": { "type": "string" }, "owns": { "type": "object", - "required": ["risks", "plugin", "skills"], + "required": ["plugin", "skills"], "properties": { - "risks": { "type": "array", "items": { "type": "string" } }, "plugin": { "type": "string" }, - "skills": { "type": "array", "items": { "type": "string" } } + "skills": { "type": "array", "items": { "type": "string" } }, + "risks": { "type": "array", "items": { "type": "string" }, "description": "gate personas only" }, + "capabilities": { "type": "array", "items": { "type": "string" }, "description": "capability personas only" } } }, "engages_when": { "type": "array", "items": { "type": "string" } }, "gate_policy": { "type": "object", + "description": "gate personas only", "required": ["net_new", "tweak", "override"], "properties": { "net_new": { "enum": ["hard", "advisory"] }, diff --git a/personas/product-manager/persona.json b/personas/product-manager/persona.json index a856669..469fc41 100644 --- a/personas/product-manager/persona.json +++ b/personas/product-manager/persona.json @@ -2,6 +2,7 @@ "id": "product-manager", "name": "Product Manager", "version": "0.1.0", + "mode": "gate", "owns": { "risks": ["value", "viability"], "plugin": "product-manager", diff --git a/scripts/audit/sources.ts b/scripts/audit/sources.ts index 97f8e39..bf2e84d 100644 --- a/scripts/audit/sources.ts +++ b/scripts/audit/sources.ts @@ -42,5 +42,6 @@ export const SOURCES: PluginSource[] = [ { plugin: "designer", editorial: true, skip: false, skills: ["designer"], packages: [] }, { plugin: "optimizer", editorial: true, skip: false, skills: ["optimizer"], packages: [] }, { plugin: "product-manager", editorial: true, skip: false, skills: ["pm-gate"], packages: [] }, + { plugin: "marketing", editorial: true, skip: false, skills: ["marketing"], packages: [] }, { plugin: "hyperstack", editorial: false, skip: true, skills: [], packages: [] }, ]; diff --git a/skills/INDEX.md b/skills/INDEX.md index c488852..c75c9b3 100644 --- a/skills/INDEX.md +++ b/skills/INDEX.md @@ -39,6 +39,7 @@ Categories: | `behaviour-analysis` | Systematic UI/UX behaviour analysis for interactive applications. Audits every user action, state transition, view mode, | | `design-patterns-skill` | Apply core programming principles and design patterns from Clean Code, The Pragmatic Programmer, Code Complete, Refactor | | `designer` | | +| `marketing` | Use to do product marketing for any brand - position it, find the message, write the copy ("marketing words"), set brand | | `readme-writer` | Writes or rewrites project README files using repository evidence instead of generic filler. Use when creating a new REA | | `security-review` | Security code review for vulnerabilities. Use when asked to "security review", "find vulnerabilities", "check for securi | | `shadcn-expert` | Advanced shadcn/ui architect specializing in Base UI, Tailwind v4, data-slot patterns, and component composition. Use wh | diff --git a/skills/hyperstack/SKILL.md b/skills/hyperstack/SKILL.md index 7583e0e..175ca77 100644 --- a/skills/hyperstack/SKILL.md +++ b/skills/hyperstack/SKILL.md @@ -248,18 +248,21 @@ The bootstrap and orchestrator (`hyper`) choose the correct role based on the re ## Layer 4: Personas -Personas are judgment lenses that OWN a class of decision and gate it before -execution. They are internal and auto-engaged by `hyper`. +Personas are domain-expert lenses that `hyper` engages for a domain. Two modes: +GATE (owns a risk and blocks, e.g. product-manager) and CAPABILITY (produces +domain output, e.g. marketing). Internal and auto-engaged. -| Persona | Owns | Gate | -|---|---|---| -| `product-manager` | value + viability product risk | hard for net-new builds, advisory for tweaks, user override always | +| Persona | Mode | Owns | Engages on | +|---|---|---|---| +| `product-manager` | gate | value + viability product risk | net-new feature/product/scope (hard gate, advisory tweaks, user override) | +| `marketing` | capability | positioning, messaging, copy, brand, GTM | position/launch/name/write-copy/grow a product, app, or website | ## Persona Registry - `product-manager` - grounds build decisions in validated customer problems (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`. --- diff --git a/skills/marketing/SKILL.md b/skills/marketing/SKILL.md new file mode 100644 index 0000000..be9b438 --- /dev/null +++ b/skills/marketing/SKILL.md @@ -0,0 +1,44 @@ +--- +name: marketing +description: Use to do product marketing for any brand - position it, find the message, write the copy ("marketing words"), set brand voice, plan go-to-market and growth. Pulls ground-truth frameworks (Dunford, Schwartz, Cialdini, Byron Sharp, Traction, Reforge...) via the marketing MCP tools and applies them. Trigger when asked to position, launch, name, write copy for, or grow a product, app, or website, or for any marketing recommendation. +category: domain +--- + +# Marketing + +The product-marketer persona's skill. Do real product marketing for any brand, +grounded in named frameworks - the marketing MCP tools supply the canon, you make the calls. + +## Core rule + +``` +POSITION before MESSAGE before COPY. +Never write words before you have decided what it is, who it is for, and why it wins. +``` + +## Workflow (new brand) + +Call `marketing_brief(brand, deliverables?)` first for the ordered plan, then: + +1. **Intake** - who is it for, what does it do, what are the competitive alternatives, what is unique. +2. **Position** (`marketing_get_positioning`) - Dunford's 5 components in order; write the positioning statement. +3. **Message** (`marketing_get_messaging`) - value-prop canvas (top pains/gains), StoryBrand (customer = hero), strategic narrative ("why now"); build the message hierarchy. +4. **Awareness** (`marketing_get_awareness_stages`) - locate the market on Schwartz's 5 stages; the more aware, the less you say. +5. **Write** (`marketing_get_copywriting_formulas` + `marketing_get_persuasion`) - pick a formula (PAS/AIDA/BAB), write headline + body + CTA; replace adjectives with proof. +6. **Voice** (`marketing_get_voice`) - pick ONE archetype + set the 4 tone dimensions; apply consistently. +7. **GTM** (`marketing_get_gtm`, `marketing_get_channels`, `marketing_get_growth_model`) - choose the motion, Bullseye the channels, design a growth loop. +8. **QA** (`marketing_get_anti_patterns`) - kill feature-dump, we-we copy, vague claims. + +## On-demand mode ("give me marketing words / a tagline / a hero section for X") + +Skip to: awareness stage -> pick a formula -> apply voice -> produce. Always name the framework you used. + +## Output discipline + +- Customer language, not company jargon (mine how they actually talk). +- Lead with the customer outcome/problem, never a feature list (use FAB). +- Every claim earns a proof point (number, named customer, demo) - no "best / seamless / world-class". +- Take a point of view; do not hedge with five safe options. +- Differentiated, not generic - if it could be any competitor's copy, rewrite it. + +## Hand back to `hyper` for review and delivery. The marketer produces; it does not ship or gate. diff --git a/src/index.ts b/src/index.ts index 36cca20..b4ad5fc 100755 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,7 @@ import { shadcnPlugin } from "./plugins/shadcn/index.js"; import { hyperstackPlugin } from "./plugins/hyperstack/index.js"; import { optimizerPlugin } from "./plugins/optimizer/index.js"; import { productManagerPlugin } from "./plugins/product-manager/index.js"; +import { marketingPlugin } from "./plugins/marketing/index.js"; import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; @@ -46,6 +47,7 @@ export const allPlugins = [ hyperstackPlugin, optimizerPlugin, productManagerPlugin, + marketingPlugin, ]; loadPlugins(server, allPlugins); diff --git a/src/internal/context-compiler.ts b/src/internal/context-compiler.ts index e7eb3e1..562c22d 100644 --- a/src/internal/context-compiler.ts +++ b/src/internal/context-compiler.ts @@ -203,7 +203,7 @@ export function compileUsingHyperstackBootstrap(source: string): { content: stri ...roleRegistry, "", "## Personas", - "- Personas are internal judgment lenses that own and gate a decision class.", + "- Personas are internal domain-expert lenses (gate or capability) that hyper auto-engages by domain.", ...personaRegistry, "", "## Routing Summary", diff --git a/src/personas/registry.ts b/src/personas/registry.ts index 57c3f76..f776a1e 100644 --- a/src/personas/registry.ts +++ b/src/personas/registry.ts @@ -10,10 +10,13 @@ export interface PersonaManifest { id: string; name: string; version: string; - owns: { risks: string[]; plugin: string; skills: string[] }; + // gate = owns a risk and blocks (e.g. product-manager); capability = produces + // domain output (e.g. marketing). Both are domain-expert lenses engaged by hyper. + mode?: "gate" | "capability"; + owns: { plugin: string; skills: string[]; risks?: string[]; capabilities?: string[] }; engaged_by?: string; engages_when: string[]; - gate_policy: { net_new: "hard" | "advisory"; tweak: "hard" | "advisory"; override: string }; + gate_policy?: { net_new: "hard" | "advisory"; tweak: "hard" | "advisory"; override: string }; } function isValid(m: unknown): m is PersonaManifest { @@ -22,8 +25,8 @@ function isValid(m: unknown): m is PersonaManifest { !!p && typeof p.id === "string" && !!p.owns && - Array.isArray(p.owns.risks) && - !!p.gate_policy + typeof p.owns.plugin === "string" && + Array.isArray(p.owns.skills) ); } diff --git a/src/plugins/marketing/data.ts b/src/plugins/marketing/data.ts new file mode 100644 index 0000000..9ba21bd --- /dev/null +++ b/src/plugins/marketing/data.ts @@ -0,0 +1,64 @@ +// Marketing persona (capability) - ground-truth frameworks + a real brief assembler. +// +// PHILOSOPHY (same as the corrected product-manager plugin): tools supply the +// canon (frameworks, formulas-with-templates, swipe structures, playbooks) that +// the agent RECALLS and APPLIES. No regex/classifier "deciders" - the marketer +// makes the call. The only logic here is a deterministic assembler that maps a +// requested deliverable to the ordered set of frameworks to apply. +// +// Corpus prose lives in snippets/*.txt via createSnippetLoader. Sources: Dunford, +// Osterwalder, Miller, Raskin, Schwartz, Ogilvy/Caples, Cialdini, Mark & Pearson, +// Byron Sharp, Binet & Field, Play Bigger, Weinberg/Mares, McClure, Balfour/Reforge. + +import { snippet } from "./loader.js"; + +const join = (...parts: string[]) => parts.join("\n\n---\n\n"); + +export const POSITIONING_DOC: string = join(snippet("positioning/dunford.txt"), snippet("positioning/styles.txt")); +export const MESSAGING_DOC: string = join(snippet("messaging/value-prop-canvas.txt"), snippet("messaging/storybrand.txt"), snippet("messaging/narrative.txt")); +export const FORMULAS_DOC: string = snippet("copywriting/formulas.txt"); +export const AWARENESS_DOC: string = snippet("copywriting/awareness-stages.txt"); +export const PERSUASION_DOC: string = join(snippet("copywriting/headlines.txt"), snippet("copywriting/persuasion.txt"), snippet("copywriting/landing-anatomy.txt")); +export const VOICE_DOC: string = join(snippet("brand/archetypes.txt"), snippet("brand/voice-tone.txt")); +export const BRAND_DOC: string = join(snippet("brand/laws.txt"), snippet("brand/category-design.txt")); +export const GTM_DOC: string = join(snippet("gtm/motions.txt"), snippet("gtm/launch.txt")); +export const CHANNELS_DOC: string = snippet("gtm/channels.txt"); +export const GROWTH_DOC: string = snippet("gtm/growth-model.txt"); +export const ANTI_PATTERNS_DOC: string = snippet("anti-patterns/marketing.txt"); + +// --- The brief assembler: deterministic, real (not a fake verdict). --- +// Given which deliverables the brand needs, return the ordered workflow steps + +// which tools to call. The agent does the actual marketing; this routes it. + +export interface BriefStep { step: string; tools: string; why: string; } + +export const FULL_WORKFLOW: BriefStep[] = [ + { step: "Position", tools: "marketing_get_positioning", why: "Dunford's 5 components in order - decide what it is and who it is for." }, + { step: "Message", tools: "marketing_get_messaging", why: "value-prop canvas + StoryBrand + strategic narrative; build the message hierarchy." }, + { step: "Awareness", tools: "marketing_get_awareness_stages", why: "match the copy to how aware the market already is." }, + { step: "Write", tools: "marketing_get_copywriting_formulas, marketing_get_persuasion", why: "pick a formula, write headline + body + CTA with proof." }, + { step: "Voice", tools: "marketing_get_voice", why: "pick a brand archetype + tone dimensions, apply consistently." }, + { step: "GTM", tools: "marketing_get_gtm, marketing_get_channels, marketing_get_growth_model", why: "choose the motion, Bullseye the channels, design a growth loop." }, + { step: "Check", tools: "marketing_get_anti_patterns", why: "no feature-dump, we-we copy, or vague unproven claims." }, +]; + +const DELIVERABLE_STEPS: Record = { + positioning: ["Position"], + messaging: ["Message", "Awareness"], + copy: ["Awareness", "Write", "Voice"], + copywriting: ["Awareness", "Write", "Voice"], + brand: ["Voice"], + gtm: ["GTM"], + growth: ["GTM"], +}; + +export function buildBrief(deliverables?: string[]): BriefStep[] { + if (!deliverables || deliverables.length === 0) return FULL_WORKFLOW; + const wanted = new Set(); + for (const d of deliverables) { + for (const s of DELIVERABLE_STEPS[d.trim().toLowerCase()] ?? []) wanted.add(s); + } + if (wanted.size === 0) return FULL_WORKFLOW; + wanted.add("Check"); + return FULL_WORKFLOW.filter((s) => wanted.has(s.step)); +} diff --git a/src/plugins/marketing/index.ts b/src/plugins/marketing/index.ts new file mode 100644 index 0000000..6a84aa9 --- /dev/null +++ b/src/plugins/marketing/index.ts @@ -0,0 +1,34 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { Plugin } from "../../registry.js"; +import { register as getPositioning } from "./tools/get-positioning.js"; +import { register as getMessaging } from "./tools/get-messaging.js"; +import { register as getCopywritingFormulas } from "./tools/get-copywriting-formulas.js"; +import { register as getAwarenessStages } from "./tools/get-awareness-stages.js"; +import { register as getPersuasion } from "./tools/get-persuasion.js"; +import { register as getVoice } from "./tools/get-voice.js"; +import { register as getBrandStrategy } from "./tools/get-brand-strategy.js"; +import { register as getGtm } from "./tools/get-gtm.js"; +import { register as getChannels } from "./tools/get-channels.js"; +import { register as getGrowthModel } from "./tools/get-growth-model.js"; +import { register as getAntiPatterns } from "./tools/get-anti-patterns.js"; +import { register as brief } from "./tools/brief.js"; + +function register(server: McpServer): void { + getPositioning(server); + getMessaging(server); + getCopywritingFormulas(server); + getAwarenessStages(server); + getPersuasion(server); + getVoice(server); + getBrandStrategy(server); + getGtm(server); + getChannels(server); + getGrowthModel(server); + getAntiPatterns(server); + brief(server); +} + +export const marketingPlugin: Plugin = { + name: "marketing", + register, +}; diff --git a/src/plugins/marketing/loader.ts b/src/plugins/marketing/loader.ts new file mode 100644 index 0000000..ff6a39f --- /dev/null +++ b/src/plugins/marketing/loader.ts @@ -0,0 +1,3 @@ +import { createSnippetLoader } from "../../shared/loader-factory.js"; + +export const snippet = createSnippetLoader("marketing"); diff --git a/src/plugins/marketing/snippets/anti-patterns/marketing.txt b/src/plugins/marketing/snippets/anti-patterns/marketing.txt new file mode 100644 index 0000000..23e1a9a --- /dev/null +++ b/src/plugins/marketing/snippets/anti-patterns/marketing.txt @@ -0,0 +1,12 @@ +# Marketing anti-patterns (red flags - catch these in any output) + +- Feature-dumping - listing features instead of customer value. Fix: FAB, end every line on the benefit. +- "We-we" copy - every sentence starts with we/our/I. Fix: make the customer the hero ("you"); use their words. +- Vague claims - "best-in-class", "world-class", "seamless", "revolutionary" with no proof. Fix: a number, a named customer, a demo. +- Marketing to everyone - targeting "everyone" so it lands with no one. Fix: pick the segment that cares most. +- Better, not different - competing on incremental "better" in a crowded category. Fix: reframe the category or own a sub-segment. +- Copy before positioning - writing words before deciding what you are and who for. Fix: position -> message -> write, in that order. +- Premature scaling - spending on a channel before it is proven, or scaling before retention works. Fix: Bullseye-test first; fix retention before acquisition. +- Vanity metrics - optimizing impressions/signups that do not tie to value or revenue. Fix: North Star + AARRR (activation, retention). +- Jargon over clarity - clever/punny copy the reader must decode. Fix: clarity beats cleverness, always. +- Out-claiming a tired market - a bigger number nobody believes. Fix: out-mechanism (explain HOW) and identify with the reader. diff --git a/src/plugins/marketing/snippets/brand/archetypes.txt b/src/plugins/marketing/snippets/brand/archetypes.txt new file mode 100644 index 0000000..3c0b1e6 --- /dev/null +++ b/src/plugins/marketing/snippets/brand/archetypes.txt @@ -0,0 +1,18 @@ +# The 12 Brand Archetypes (Mark & Pearson) - pick ONE primary (optionally one secondary) for a consistent personality. + +| archetype | core desire | voice | example | +|---|---|---|---| +| Innocent | safety, simplicity | optimistic, honest, pure | Dove | +| Sage | truth, understanding | wise, measured, expert | Google | +| Explorer | freedom, discovery | adventurous, bold, restless | Patagonia | +| Outlaw | revolution, disruption | rebellious, raw, defiant | Harley-Davidson | +| Magician | transformation, wonder | visionary, charismatic | Disney | +| Hero | mastery, courage | bold, confident, motivating | Nike | +| Lover | intimacy, connection | warm, sensual, expressive | Chanel | +| Jester | enjoyment, fun | playful, irreverent, witty | Old Spice | +| Everyman | belonging | down-to-earth, friendly, real | IKEA | +| Caregiver | service, protection | reassuring, nurturing | Johnson & Johnson | +| Ruler | control, order | authoritative, polished, premium | Rolex | +| Creator | innovation, self-expression | imaginative, inventive | Lego, Adobe | + +Rule: ONE primary archetype, applied everywhere. It sets the vocabulary, tone, imagery, and - just as important - what the brand would never say. diff --git a/src/plugins/marketing/snippets/brand/category-design.txt b/src/plugins/marketing/snippets/brand/category-design.txt new file mode 100644 index 0000000..fbad661 --- /dev/null +++ b/src/plugins/marketing/snippets/brand/category-design.txt @@ -0,0 +1,16 @@ +# Category Design (Play Bigger / Category Pirates) - "different, not better" + +Thesis: you win by creating and dominating a NEW/DIFFERENT category, not by being an incremental "better". In tech categories the category king captures ~76% of the total market-cap value created. + +## Three levers +1. POV (point of view) - a distinct world-view that names the problem the old category ignores and frames a new one. Start by REJECTING the premise of the old category. +2. Languaging - name the category and the problem; own the words. Put the CATEGORY first, your brand second. +3. Lightning Strike - a concentrated go-to-market moment (event, launch, campaign) that teaches the market the new category exists. + +## Named moves +- Start rejecting the premise (of the old category). +- Frame, name, and claim the category. +- Put your category first, your brand second. +- Be different, not better. + +Use ONLY when you can credibly create demand for a new category (big bet, multi-year). Otherwise default to big-fish-small-pond positioning. diff --git a/src/plugins/marketing/snippets/brand/laws.txt b/src/plugins/marketing/snippets/brand/laws.txt new file mode 100644 index 0000000..d869184 --- /dev/null +++ b/src/plugins/marketing/snippets/brand/laws.txt @@ -0,0 +1,13 @@ +# Evidence-based brand laws (Byron Sharp, How Brands Grow; Binet & Field) + +## Byron Sharp - how brands actually grow (from data, not theory) +- Growth comes from PENETRATION (more buyers), not loyalty. Acquire light and non-buyers; do not just milk heavy users. +- Mental availability - be easy to BRING TO MIND in a buying moment. Build it with consistent Distinctive Brand Assets (logo, color, character, tagline, sound) used relentlessly over years. +- Physical availability - be easy to FIND and BUY (distribution, presence in every relevant channel/moment). +- Double jeopardy law - smaller brands have fewer buyers AND slightly lower loyalty. The lever is therefore reach/penetration, not loyalty programs. +- Reach broadly and consistently; do not narrow your advertising to a tiny "target" - sell to all the category's buyers. + +## Binet & Field - The Long and the Short of It +- Split budget ~60% brand-building (emotional, broad reach, long-term) / ~40% sales activation (rational, targeted, short-term). +- Brand-building compounds; activation spikes then fades. Activation-only slowly starves growth. +- Emotional campaigns beat rational ones on long-term business effects (and are more efficient at scale). diff --git a/src/plugins/marketing/snippets/brand/voice-tone.txt b/src/plugins/marketing/snippets/brand/voice-tone.txt new file mode 100644 index 0000000..8b23225 --- /dev/null +++ b/src/plugins/marketing/snippets/brand/voice-tone.txt @@ -0,0 +1,18 @@ +# Brand voice & tone + +VOICE = constant (the brand's personality, set by the archetype). TONE = varies by context (a celebration vs an error message). + +## Define voice on 4 tone dimensions (Nielsen Norman Group) +1. Funny <-> serious +2. Formal <-> casual +3. Respectful <-> irreverent +4. Enthusiastic <-> matter-of-fact + +Pick a point on each axis; that is the voice. Then write a "we are / we are not" table and 3-5 example rewrites so anyone can apply it. + +## Apply consistently +- Vocabulary - words we use, words we never use. +- Sentence rhythm and length. +- How we handle errors, empty states, confirmations, and bad news. + +Tone shifts with the user's emotional state (a frustrated user = drop the jokes); voice never changes. diff --git a/src/plugins/marketing/snippets/copywriting/awareness-stages.txt b/src/plugins/marketing/snippets/copywriting/awareness-stages.txt new file mode 100644 index 0000000..871c303 --- /dev/null +++ b/src/plugins/marketing/snippets/copywriting/awareness-stages.txt @@ -0,0 +1,12 @@ +# 5 Stages of Market Awareness (Eugene Schwartz, Breakthrough Advertising) +# Rule: the MORE aware the market, the LESS you need to say. Match the message to the stage. + +1. Unaware - does not know they have the problem. Lead with a story or insight that NAMES the problem. Sell the problem first, not the product. +2. Problem-aware - feels the pain, does not know solutions exist. Lead with the problem + agitate, then reveal that a solution exists. +3. Solution-aware - knows solution types exist, is shopping around. Lead with your MECHANISM - why your approach wins over the other approaches. +4. Product-aware - knows your product, on the fence. Lead with proof, differentiation, the offer, and risk reversal. +5. Most-aware - ready, just needs a reason now. Lead with the deal, the CTA, urgency. Shortest copy. + +## Market sophistication (how many claims they have already heard) - escalate accordingly: +- Fresh market: state the claim plainly. +- Tired market: do not out-claim it (no one believes a bigger number) - out-MECHANISM it (explain HOW), then identify with the reader's exact situation. diff --git a/src/plugins/marketing/snippets/copywriting/formulas.txt b/src/plugins/marketing/snippets/copywriting/formulas.txt new file mode 100644 index 0000000..0367074 --- /dev/null +++ b/src/plugins/marketing/snippets/copywriting/formulas.txt @@ -0,0 +1,23 @@ +# Copywriting formulas (with fill-in templates). Hybridize freely: PAS hook + FAB body + AIDA close. + +## PAS - Problem, Agitate, Solution (best default for pain-driven copy) +[Problem they feel] -> [Agitate: what it costs them, twist the knife] -> [Solution: your product removes it]. + +## AIDA - Attention, Interest, Desire, Action +[Attention: hook] -> [Interest: relevant detail] -> [Desire: the better future] -> [Action: CTA]. + +## BAB - Before, After, Bridge +[Before: their painful now] -> [After: the better world] -> [Bridge: your product is how they cross over]. + +## FAB - Feature, Advantage, Benefit (kills feature-dumping) +[Feature] which means [Advantage] so you can [Benefit to the customer]. Always land on the benefit. + +## PASTOR - Problem, Amplify, Story/Solution, Transformation, Offer, Response (long-form / sales pages) +Problem -> Amplify the cost of inaction -> Story + Solution -> Transformation (proof others got it) -> Offer -> Response (the ask). + +## 4 Ps - Picture, Promise, Prove, Push +Picture the desired outcome -> Promise it -> Prove it (evidence) -> Push (CTA + reason to act now). + +## The 4 U's (run every headline through this): is it Useful, Urgent, Unique, and Ultra-specific? + +## Rule (the "slippery slide", Sugarman): the only job of the first sentence is to get the second one read. Every line earns the next. diff --git a/src/plugins/marketing/snippets/copywriting/headlines.txt b/src/plugins/marketing/snippets/copywriting/headlines.txt new file mode 100644 index 0000000..4c014f5 --- /dev/null +++ b/src/plugins/marketing/snippets/copywriting/headlines.txt @@ -0,0 +1,19 @@ +# Headlines (Ogilvy, Caples) - 5x as many people read the headline as the body. It is 80% of the work. + +## Ogilvy's rules +- The headline promises a BENEFIT or news, and flags the target audience. +- Put the brand/product in the headline. +- Specifics beat generalities (numbers, named outcomes, timeframes). +- "How to", "Introducing/Now/Announcing" (news), and bracketed clarifiers pull well. +- Never sacrifice clarity for cleverness. A pun the reader must decode is a dead headline. + +## Caples' proven headline types +Direct ("Half-price sale") · News ("Announcing...") · How-to ("How to [outcome] without [pain]") · Question ("Do you make these mistakes?") · Command ("Stop wasting...") · Reason-why ("7 reasons...") · Testimonial ("I [result] in [time]"). + +## Swipe templates (fill in) +- How to [desired outcome] without [pain/objection]. +- [Number] ways to [outcome] (even if [obstacle]). +- The [adjective] way to [outcome]. +- Do you [problem]? Here is [solution]. +- Get [outcome] in [timeframe] - or [risk reversal]. +- [Audience]: [the one thing they want], finally. diff --git a/src/plugins/marketing/snippets/copywriting/landing-anatomy.txt b/src/plugins/marketing/snippets/copywriting/landing-anatomy.txt new file mode 100644 index 0000000..3d65883 --- /dev/null +++ b/src/plugins/marketing/snippets/copywriting/landing-anatomy.txt @@ -0,0 +1,13 @@ +# Landing page anatomy (conversion order) + +1. Hero: headline (the #1 value/outcome) + subhead (who it is for + how) + primary CTA + a hero visual/proof. Pass the 5-second test: a stranger instantly gets what it is, who it is for, why care. +2. Social-proof bar: logos / user counts / ratings directly under the hero. +3. Problem -> solution: name the pain in the customer's words, then show the mechanism. +4. Benefits, not features: FAB - lead each block with the outcome. +5. How it works: 3 simple steps (the "plan" - reduce perceived effort). +6. Proof: testimonials, case studies, data, before/after. +7. Objection handling / FAQ: kill the top 3-5 reasons NOT to buy (price, risk, switching cost, trust). +8. Risk reversal: guarantee / free trial / "cancel anytime". +9. Final CTA: restate the core value + the single action. + +Above the fold must answer: what is it, who is it for, what do I get, what do I do next. One primary action per page. diff --git a/src/plugins/marketing/snippets/copywriting/persuasion.txt b/src/plugins/marketing/snippets/copywriting/persuasion.txt new file mode 100644 index 0000000..062635c --- /dev/null +++ b/src/plugins/marketing/snippets/copywriting/persuasion.txt @@ -0,0 +1,11 @@ +# Persuasion - Cialdini's 7 principles of influence. Use ethically; weave into copy and offers. + +1. Reciprocity - give first (free value, tools, useful content); people return the favor. +2. Commitment & consistency - get a small yes first; people stay consistent with prior actions and stated identity. +3. Social proof - show others (especially SIMILAR others) doing it: counts, logos, reviews, testimonials, "most popular". +4. Authority - signal credible expertise: credentials, data, named experts, press, demonstrated competence. +5. Liking - we say yes to those we like: similarity, genuine specifics, shared identity, a real human voice. +6. Scarcity - limited quantity / time / access raises desire. Must be REAL or it destroys trust. +7. Unity - shared identity ("we", "people like us", a movement or category). The strongest, most modern trigger. + +Rule: proof beats adjectives. Replace every "best / leading / amazing / world-class" with a specific number, a named customer, or a live demonstration. diff --git a/src/plugins/marketing/snippets/gtm/channels.txt b/src/plugins/marketing/snippets/gtm/channels.txt new file mode 100644 index 0000000..723a58d --- /dev/null +++ b/src/plugins/marketing/snippets/gtm/channels.txt @@ -0,0 +1,13 @@ +# Channels: the 19 traction channels + the Bullseye framework (Traction, Weinberg & Mares) + +## The 19 channels (scan ALL - do not default to the 1-2 you already know) +Viral marketing · PR · Unconventional PR (stunts) · SEM (paid search) · Social & display ads · Offline ads · SEO · Content marketing · Email marketing · Engineering as marketing (free tools/calculators) · Targeting blogs · Business development (partnerships) · Sales · Affiliate programs · Existing platforms (app stores, marketplaces) · Trade shows · Offline events · Speaking engagements · Community building. + +## Bullseye framework (how to choose) +1. Brainstorm - a realistic idea for EVERY one of the 19 channels (the outer ring). +2. Rank into 3 columns: A promising (inner ring), B possible, C long-shot. +3. Prioritize - pick the ~3 most promising for the middle ring. +4. Test - cheap, fast, parallel experiments on those 3 (measure cost, conversion, volume, payback). +5. Focus - pour resources into the ONE channel that is working (the bullseye). + +Rule: most startups get traction from a SINGLE channel at a time, and it is usually not the one you assumed. Test before you scale. diff --git a/src/plugins/marketing/snippets/gtm/growth-model.txt b/src/plugins/marketing/snippets/gtm/growth-model.txt new file mode 100644 index 0000000..8526de9 --- /dev/null +++ b/src/plugins/marketing/snippets/gtm/growth-model.txt @@ -0,0 +1,17 @@ +# Growth model: AARRR metrics + growth loops + +## AARRR "pirate metrics" (Dave McClure) - instrument the whole journey +- Acquisition - users arrive (from which channel?). +- Activation - first valuable experience (the "aha"); the #1 lever for most products. +- Retention - they come back (the FOUNDATION - without it the funnel leaks faster than you can fill it). +- Referral - they bring others. +- Revenue - they pay. +Find the leakiest step first; fix activation/retention before pouring money into acquisition. + +## Growth loops > funnels (Brian Balfour / Reforge) +A funnel is linear (pour in the top, leak out the bottom). A LOOP reinvests the output back into the input: + new users -> take an action producing an output (content, invites, data, revenue) -> that output drives NEW acquisition -> repeat. +Loop types: viral (users invite users), content (UGC -> indexable pages -> SEO -> users), paid (revenue funds ads that acquire revenue-generating users). +Design at least one loop; loops compound, funnels do not. + +## North Star metric: one number capturing delivered customer value (nights booked, messages sent, docs created). Align the team to move it. diff --git a/src/plugins/marketing/snippets/gtm/launch.txt b/src/plugins/marketing/snippets/gtm/launch.txt new file mode 100644 index 0000000..e4b2771 --- /dev/null +++ b/src/plugins/marketing/snippets/gtm/launch.txt @@ -0,0 +1,13 @@ +# Product launch + +## Tier the launch to the news (do not tentpole a minor update) +- Tier 1 / tentpole - major product or category moment: full narrative, press, event, coordinated channels, exec involvement. +- Tier 2 / standard - notable feature: blog + email + social + in-app + sales enablement. +- Tier 3 / soft - minor update: changelog + in-app note. + +## Phases +1. Pre-launch - positioning + narrative locked, assets ready, beta/early access, build a waitlist/audience, brief sales & support. +2. Launch - coordinate ALL channels in one window: homepage, email, social, PR, Product Hunt/community, in-app. One message, many surfaces. +3. Post-launch - measure (signups, activation, coverage), follow-up content, nurture, iterate the message on what actually landed. + +Rule: a launch is a MESSAGE moment, not a feature dump. Lead with the customer outcome and the "why now", never the feature list. diff --git a/src/plugins/marketing/snippets/gtm/motions.txt b/src/plugins/marketing/snippets/gtm/motions.txt new file mode 100644 index 0000000..e609f22 --- /dev/null +++ b/src/plugins/marketing/snippets/gtm/motions.txt @@ -0,0 +1,13 @@ +# Go-to-market motions - pick by deal size, complexity, and who decides. + +| motion | fits when | engine | +|---|---|---| +| Product-led (PLG) | low price, self-serve, fast time-to-value, individual/team adopts | the product IS the funnel: free/trial -> activation -> expansion. Optimize onboarding + the "aha" moment. | +| Sales-led | high ACV, complex, multi-stakeholder, security/procurement | reps + demos + pilots; marketing feeds pipeline (demand gen, MQL -> SQL). | +| Marketing-led | mid-market, content + brand drive inbound | content/SEO/ads build demand, light-touch sales closes. | +| Community-led | developer / prosumer / passion categories | community, advocacy, and word of mouth drive adoption. | + +## Rules +- Short time-to-value + low price -> PLG. High price + buying committee -> sales-led. +- Do not bolt enterprise sales onto a product nobody activates in; do not put PLG friction in front of a $100k deal. +- The motion must match the product: a self-serve product needs self-serve GTM. diff --git a/src/plugins/marketing/snippets/messaging/narrative.txt b/src/plugins/marketing/snippets/messaging/narrative.txt new file mode 100644 index 0000000..92f5e38 --- /dev/null +++ b/src/plugins/marketing/snippets/messaging/narrative.txt @@ -0,0 +1,16 @@ +# Strategic narrative (Andy Raskin) - the company story IS the company strategy + +A great strategic narrative (sales decks, fundraising, recruiting, the homepage spine) runs: +1. Name the big, undeniable SHIFT in the world (the "why now"). Make it feel inevitable. +2. Show there will be winners and losers because of that shift (raise the stakes - tension). +3. Tease the Promised Land - the desirable, hard-to-reach future the customer wants. This is a future STATE, not your product. +4. Introduce your capabilities as "magic gifts" - the features that help the hero reach the Promised Land. +5. Present evidence you can make the story come true (proof, customers, traction, demos). + +Lead with the shift and the stakes, NOT the product. One story aligns sales, marketing, product, and fundraising. + +## Message hierarchy (build once positioning is set) +- 1 core message (the single thing you want remembered) -> +- 3 supporting pillars (the proof themes) -> +- evidence under each pillar (features, stats, proof points, customer quotes). +Everything you write maps to this tree. If it does not ladder up to the core message, cut it. diff --git a/src/plugins/marketing/snippets/messaging/storybrand.txt b/src/plugins/marketing/snippets/messaging/storybrand.txt new file mode 100644 index 0000000..17262fc --- /dev/null +++ b/src/plugins/marketing/snippets/messaging/storybrand.txt @@ -0,0 +1,13 @@ +# StoryBrand SB7 (Donald Miller) - the customer is the hero, the brand is the guide + +Cast the message as a story where the CUSTOMER is the hero (never the brand): +1. A Character (the customer) who wants something. +2. has a Problem - on 3 levels: external (the tangible problem), internal (how it makes them feel), philosophical (why it is just plain wrong). +3. meets a Guide (your brand) who shows empathy + authority. +4. who gives them a Plan (clear, simple steps to buy and succeed). +5. and Calls them to Action (a direct CTA + a transitional CTA for the not-ready). +6. that helps them avoid Failure (the stakes - what is at risk if they do nothing). +7. and ends in Success (the transformation - who they become). + +Rule: the brand is the GUIDE, not the hero. Lead with the customer's want + problem, not your features. +One-liner template: "Most [customers] struggle with [problem]. We offer [solution] so you can [success]." diff --git a/src/plugins/marketing/snippets/messaging/value-prop-canvas.txt b/src/plugins/marketing/snippets/messaging/value-prop-canvas.txt new file mode 100644 index 0000000..b111cca --- /dev/null +++ b/src/plugins/marketing/snippets/messaging/value-prop-canvas.txt @@ -0,0 +1,15 @@ +# Value Proposition Canvas (Osterwalder / Strategyzer) + +Two sides that must FIT. + +## Customer profile (their world - observe, do not invent) +- Jobs - the functional, social, AND emotional tasks the customer is trying to get done. +- Pains - frustrations, risks, obstacles before/during/after the job. +- Gains - outcomes and benefits they want (required, expected, desired, and unexpected). + +## Value map (your offer) +- Products & services - what you offer. +- Pain relievers - how your offer removes SPECIFIC customer pains (map each reliever to a pain). +- Gain creators - how your offer produces SPECIFIC gains (map each creator to a gain). + +FIT = your pain relievers and gain creators address the customer's TOP-RANKED pains and gains, not all of them. Rank by importance to the customer, then build the message around the top 2-3. diff --git a/src/plugins/marketing/snippets/positioning/dunford.txt b/src/plugins/marketing/snippets/positioning/dunford.txt new file mode 100644 index 0000000..fe43314 --- /dev/null +++ b/src/plugins/marketing/snippets/positioning/dunford.txt @@ -0,0 +1,20 @@ +# Positioning (April Dunford, Obviously Awesome) + +Positioning = making your product the obvious leader at delivering something a +well-defined set of customers cares a lot about. It is NOT a tagline, messaging, +or brand story - those come AFTER positioning. + +## The 5 components +1. Competitive alternatives - what customers would do if you did not exist (not just rival products: a spreadsheet, an agency, hiring someone, doing nothing). +2. Unique attributes - features/capabilities you have that the alternatives lack. +3. Value - the benefit those attributes enable for the customer. Ask "so what, for the customer?" on every attribute until you reach a real outcome. +4. Target customer segment - the customers who care MOST about that value (the ones who buy fast and tell their friends). +5. Market category - the context you put the product in so the value is obvious to the target. + +## Process (run IN THIS ORDER) +competitive alternatives -> unique attributes -> value ("so what?") -> target (who cares most) -> market category (chosen LAST, deliberately). + +Choosing the category last is the key move: the category frames everything, so pick the frame that makes your unique value obvious. + +## Output: a positioning statement the whole team can repeat +For [target] who [need], [product] is the [category] that [unique value], unlike [alternative], because [unique attribute]. diff --git a/src/plugins/marketing/snippets/positioning/styles.txt b/src/plugins/marketing/snippets/positioning/styles.txt new file mode 100644 index 0000000..15188bf --- /dev/null +++ b/src/plugins/marketing/snippets/positioning/styles.txt @@ -0,0 +1,7 @@ +# Three styles of positioning (Dunford) - pick the frame that fits + +1. Head to head - position to WIN an existing category. Use only if you can credibly be the leader. Risky for challengers. +2. Big fish, small pond - position in a SUB-SEGMENT of an existing category you can dominate. Safer; own a niche first, then expand. +3. Create a new category - define and lead a brand-new category. Highest risk/reward; requires educating the market (see category design) and is a multi-year bet. + +Rule: most products should NOT create a category. Default to dominating a sub-segment (big fish, small pond) before anything bigger. diff --git a/src/plugins/marketing/tools/brief.ts b/src/plugins/marketing/tools/brief.ts new file mode 100644 index 0000000..ddf10d7 --- /dev/null +++ b/src/plugins/marketing/tools/brief.ts @@ -0,0 +1,24 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { buildBrief } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "marketing_brief", + "Assemble a product-marketing brief for a specific brand: returns the ordered workflow (which frameworks/tools to apply, in sequence) for the deliverables you need. It routes the work - you produce the marketing.", + { + brand: z.string().describe("The brand/product to market (one line: what it is + who it is for if known)."), + deliverables: z.array(z.string()).optional().describe("Subset of: positioning, messaging, copy, brand, gtm. Omit for the full workflow."), + }, + async ({ brand, deliverables }) => { + const steps = buildBrief(deliverables); + let text = `# Marketing brief: ${brand}\n\n`; + text += `Apply these in order. Each step names the tool(s) that supply the framework; you do the marketing.\n\n`; + steps.forEach((s, i) => { + text += `${i + 1}. **${s.step}** - ${s.why}\n tools: \`${s.tools}\`\n`; + }); + text += `\nStart with intake: who is it for, what does it do, what are the competitive alternatives, what is unique.\n`; + return { content: [{ type: "text" as const, text }] }; + }, + ); +} diff --git a/src/plugins/marketing/tools/get-anti-patterns.ts b/src/plugins/marketing/tools/get-anti-patterns.ts new file mode 100644 index 0000000..07d0b6b --- /dev/null +++ b/src/plugins/marketing/tools/get-anti-patterns.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { ANTI_PATTERNS_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "marketing_get_anti_patterns", + "Marketing red flags with fixes: feature-dumping, we-we copy, vague unproven claims, marketing-to-everyone, better-not-different, premature scaling, vanity metrics. Use to QA any marketing output before shipping.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Marketing anti-patterns\n\n${ANTI_PATTERNS_DOC}\n` }] }), + ); +} diff --git a/src/plugins/marketing/tools/get-awareness-stages.ts b/src/plugins/marketing/tools/get-awareness-stages.ts new file mode 100644 index 0000000..f2e9744 --- /dev/null +++ b/src/plugins/marketing/tools/get-awareness-stages.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { AWARENESS_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "marketing_get_awareness_stages", + "Eugene Schwartz's 5 stages of market awareness (unaware -> most-aware) + market sophistication, with how the copy must change per stage. Use to decide what to say before writing.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Market awareness\n\n${AWARENESS_DOC}\n` }] }), + ); +} diff --git a/src/plugins/marketing/tools/get-brand-strategy.ts b/src/plugins/marketing/tools/get-brand-strategy.ts new file mode 100644 index 0000000..99742f7 --- /dev/null +++ b/src/plugins/marketing/tools/get-brand-strategy.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { BRAND_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "marketing_get_brand_strategy", + "Evidence-based brand strategy: Byron Sharp's laws (penetration over loyalty, mental & physical availability, distinctive assets, double jeopardy), Binet & Field's 60/40 brand-vs-activation split, and category design (different-not-better). Use for the longer game.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Brand strategy\n\n${BRAND_DOC}\n` }] }), + ); +} diff --git a/src/plugins/marketing/tools/get-channels.ts b/src/plugins/marketing/tools/get-channels.ts new file mode 100644 index 0000000..26ba57f --- /dev/null +++ b/src/plugins/marketing/tools/get-channels.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { CHANNELS_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "marketing_get_channels", + "The 19 traction channels + the Bullseye framework (Traction, Weinberg & Mares): scan all channels, rank, test ~3 cheaply, focus on the one that works. Use to choose acquisition channels.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Channels\n\n${CHANNELS_DOC}\n` }] }), + ); +} diff --git a/src/plugins/marketing/tools/get-copywriting-formulas.ts b/src/plugins/marketing/tools/get-copywriting-formulas.ts new file mode 100644 index 0000000..4a80969 --- /dev/null +++ b/src/plugins/marketing/tools/get-copywriting-formulas.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { FORMULAS_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "marketing_get_copywriting_formulas", + "Copywriting formulas with fill-in templates: PAS, AIDA, BAB, FAB, PASTOR, 4 Ps, the 4 U's. Use to structure any headline, ad, landing page, or email. Apply the formula yourself - this supplies the templates.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Copywriting formulas\n\n${FORMULAS_DOC}\n` }] }), + ); +} diff --git a/src/plugins/marketing/tools/get-growth-model.ts b/src/plugins/marketing/tools/get-growth-model.ts new file mode 100644 index 0000000..07f7704 --- /dev/null +++ b/src/plugins/marketing/tools/get-growth-model.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { GROWTH_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "marketing_get_growth_model", + "Growth model: AARRR pirate metrics (acquisition/activation/retention/referral/revenue) + growth loops vs funnels (Reforge) + the North Star metric. Use to design how growth compounds.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Growth model\n\n${GROWTH_DOC}\n` }] }), + ); +} diff --git a/src/plugins/marketing/tools/get-gtm.ts b/src/plugins/marketing/tools/get-gtm.ts new file mode 100644 index 0000000..6dc55b0 --- /dev/null +++ b/src/plugins/marketing/tools/get-gtm.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { GTM_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "marketing_get_gtm", + "Go-to-market motions (PLG vs sales-led vs marketing-led vs community-led, and when each fits) + the product-launch playbook (launch tiers and pre/launch/post phases). Use to decide how it reaches the market.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Go-to-market\n\n${GTM_DOC}\n` }] }), + ); +} diff --git a/src/plugins/marketing/tools/get-messaging.ts b/src/plugins/marketing/tools/get-messaging.ts new file mode 100644 index 0000000..9d97106 --- /dev/null +++ b/src/plugins/marketing/tools/get-messaging.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { MESSAGING_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "marketing_get_messaging", + "Messaging frameworks: value-proposition canvas (jobs/pains/gains), StoryBrand SB7 (customer is the hero), strategic narrative (Raskin - story = strategy), and the message hierarchy. Use after positioning is set.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Messaging\n\n${MESSAGING_DOC}\n` }] }), + ); +} diff --git a/src/plugins/marketing/tools/get-persuasion.ts b/src/plugins/marketing/tools/get-persuasion.ts new file mode 100644 index 0000000..72e71ad --- /dev/null +++ b/src/plugins/marketing/tools/get-persuasion.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { PERSUASION_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "marketing_get_persuasion", + "Persuasion + headlines + landing-page anatomy: Ogilvy/Caples headline rules and swipe templates, Cialdini's 7 principles of influence, and the conversion-ordered landing page structure. Use while writing.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Persuasion, headlines & landing pages\n\n${PERSUASION_DOC}\n` }] }), + ); +} diff --git a/src/plugins/marketing/tools/get-positioning.ts b/src/plugins/marketing/tools/get-positioning.ts new file mode 100644 index 0000000..3e372ae --- /dev/null +++ b/src/plugins/marketing/tools/get-positioning.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { POSITIONING_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "marketing_get_positioning", + "Positioning framework (April Dunford): the 5 components in order (competitive alternatives -> unique attributes -> value -> target -> category) + the 3 positioning styles + a positioning-statement template. Use FIRST, before any messaging or copy.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Positioning\n\n${POSITIONING_DOC}\n` }] }), + ); +} diff --git a/src/plugins/marketing/tools/get-voice.ts b/src/plugins/marketing/tools/get-voice.ts new file mode 100644 index 0000000..301d7bd --- /dev/null +++ b/src/plugins/marketing/tools/get-voice.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { VOICE_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "marketing_get_voice", + "Brand voice: the 12 archetypes (Mark & Pearson) with core desire/voice/example, plus the 4 tone dimensions (Nielsen Norman) for defining and applying a consistent voice. Use to set how the brand sounds.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Brand voice\n\n${VOICE_DOC}\n` }] }), + ); +} diff --git a/tests/marketing-behaviour.test.ts b/tests/marketing-behaviour.test.ts new file mode 100644 index 0000000..b69a930 --- /dev/null +++ b/tests/marketing-behaviour.test.ts @@ -0,0 +1,54 @@ +import { test, expect } from "bun:test"; +import { buildBrief, FULL_WORKFLOW } from "../src/plugins/marketing/data.ts"; +import { captureTool, extractTextContent } from "./helpers.ts"; +import { register as getPositioning } from "../src/plugins/marketing/tools/get-positioning.ts"; +import { register as getFormulas } from "../src/plugins/marketing/tools/get-copywriting-formulas.ts"; +import { register as getChannels } from "../src/plugins/marketing/tools/get-channels.ts"; +import { register as brief } from "../src/plugins/marketing/tools/brief.ts"; + +// --- the brief assembler --- +test("full workflow leads with positioning", () => { + expect(FULL_WORKFLOW[0].step).toBe("Position"); +}); + +test("buildBrief returns the full workflow when no deliverables given", () => { + expect(buildBrief()).toEqual(FULL_WORKFLOW); +}); + +test("buildBrief for copy returns write+voice+check, not positioning", () => { + const steps = buildBrief(["copy"]).map((s) => s.step); + expect(steps).toContain("Write"); + expect(steps).toContain("Voice"); + expect(steps).toContain("Check"); + expect(steps).not.toContain("Position"); +}); + +// --- tools serve ground-truth frameworks --- +test("get_positioning returns Dunford's components", async () => { + const tool = captureTool(getPositioning); + expect(tool.name).toBe("marketing_get_positioning"); + const text = extractTextContent(await tool.invoke({})); + expect(text).toMatch(/competitive alternatives/i); + expect(text).toMatch(/market category/i); +}); + +test("get_copywriting_formulas returns PAS and AIDA templates", async () => { + const tool = captureTool(getFormulas); + const text = extractTextContent(await tool.invoke({})); + expect(text).toMatch(/PAS/); + expect(text).toMatch(/AIDA/); +}); + +test("get_channels returns the Bullseye framework", async () => { + const tool = captureTool(getChannels); + const text = extractTextContent(await tool.invoke({})); + expect(text).toMatch(/Bullseye/i); +}); + +test("brief assembles an ordered plan for a specific brand", async () => { + const tool = captureTool(brief); + expect(tool.name).toBe("marketing_brief"); + const text = extractTextContent(await tool.invoke({ brand: "a meditation app for busy parents" })); + expect(text).toMatch(/meditation app/); + expect(text).toMatch(/Position/); +}); diff --git a/tests/persona-registry-behaviour.test.ts b/tests/persona-registry-behaviour.test.ts index 712a880..dc536d7 100644 --- a/tests/persona-registry-behaviour.test.ts +++ b/tests/persona-registry-behaviour.test.ts @@ -9,6 +9,14 @@ test("registry loads the product-manager persona manifest", () => { expect(pm!.gate_policy.net_new).toBe("hard"); }); +test("registry loads the marketing capability persona", () => { + const personas = loadPersonas(); + const m = personas.find((p) => p.id === "marketing"); + expect(m).toBeDefined(); + expect(m!.mode).toBe("capability"); + expect(m!.owns.plugin).toBe("marketing"); +}); + test("a malformed manifest is skipped, not thrown", () => { expect(() => loadPersonas()).not.toThrow(); }); diff --git a/tests/plugin-registry-behaviour.test.ts b/tests/plugin-registry-behaviour.test.ts index a1e10c8..3f89fba 100644 --- a/tests/plugin-registry-behaviour.test.ts +++ b/tests/plugin-registry-behaviour.test.ts @@ -17,10 +17,10 @@ function getRegisteredTools() { return tools; } -test("all 14 plugins register at least one tool", () => { +test("all 15 plugins register at least one tool", () => { const tools = getRegisteredTools(); const pluginPrefixes = new Set(tools.map((t) => t.name.split("_")[0])); - expect(pluginPrefixes.size).toBe(14); + expect(pluginPrefixes.size).toBe(15); }); test("product-manager plugin registers its gate tools", () => { @@ -30,6 +30,13 @@ test("product-manager plugin registers its gate tools", () => { expect(toolNames.has("product_manager_resolve_product_decision")).toBe(true); }); +test("marketing plugin registers its tools", () => { + const tools = getRegisteredTools(); + const toolNames = new Set(tools.map((t) => t.name)); + expect(toolNames.has("marketing_get_positioning")).toBe(true); + expect(toolNames.has("marketing_brief")).toBe(true); +}); + test("every registered tool has a non-empty name and description", () => { const tools = getRegisteredTools(); for (const tool of tools) { From 82f0d504393c5e15f2b05d7315b68fae21e01c99 Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Mon, 22 Jun 2026 01:41:03 +0530 Subject: [PATCH 12/16] feat(marketing): close corpus gaps - VoC, ICP, pricing, unit economics (secondary pass) Adds the highest-value learnings missed in the first build: - Voice-of-Customer / message mining (Copyhackers) - the method for getting marketing words straight from real customers (reviews/support/calls). The literal 'marketing words from learnings' engine. - ICP definition (firmographics, triggers, early-vangelist, anti-ICP). - Pricing & packaging (value-based, good-better-best, anchoring, fences, value metric). - Unit economics (CAC/LTV/payback benchmarks) + the single-minded 'only' proposition. 3 new tools (15 total), 3 new snippets + 2 enhanced (22 total). Workflow now leads with VoC research. bun test 73 pass, tsc clean. --- personas/marketing/LIFECYCLE.md | 6 ++--- skills/marketing/SKILL.md | 6 ++--- src/plugins/marketing/data.ts | 17 +++++++++----- src/plugins/marketing/index.ts | 6 +++++ .../marketing/snippets/gtm/growth-model.txt | 7 ++++++ .../marketing/snippets/gtm/pricing.txt | 19 +++++++++++++++ .../snippets/positioning/dunford.txt | 3 +++ .../marketing/snippets/positioning/icp.txt | 18 +++++++++++++++ .../snippets/research/voice-of-customer.txt | 23 +++++++++++++++++++ src/plugins/marketing/tools/get-icp.ts | 11 +++++++++ src/plugins/marketing/tools/get-pricing.ts | 11 +++++++++ .../marketing/tools/get-voice-of-customer.ts | 11 +++++++++ tests/marketing-behaviour.test.ts | 13 +++++++++-- 13 files changed, 137 insertions(+), 14 deletions(-) create mode 100644 src/plugins/marketing/snippets/gtm/pricing.txt create mode 100644 src/plugins/marketing/snippets/positioning/icp.txt create mode 100644 src/plugins/marketing/snippets/research/voice-of-customer.txt create mode 100644 src/plugins/marketing/tools/get-icp.ts create mode 100644 src/plugins/marketing/tools/get-pricing.ts create mode 100644 src/plugins/marketing/tools/get-voice-of-customer.ts diff --git a/personas/marketing/LIFECYCLE.md b/personas/marketing/LIFECYCLE.md index fc8feee..7884f50 100644 --- a/personas/marketing/LIFECYCLE.md +++ b/personas/marketing/LIFECYCLE.md @@ -4,13 +4,13 @@ - positioning, messaging, copy, brand voice, GTM/growth, or "marketing words for X" requests ## Full workflow (new brand, in order) -1. Intake: who is it for, what does it do, what are the competitive alternatives, what is unique. -2. Position: `marketing_get_positioning` - Dunford's 5 components in sequence (competitive alternatives -> unique attributes -> value -> target segment -> market category). +1. Intake + VoC: gather who/what/alternatives/unique, then `marketing_get_voice_of_customer` - mine the customer's EXACT words from reviews/support/calls (the raw material for everything). +2. Position: `marketing_get_positioning` + `marketing_get_icp` - Dunford's 5 components in sequence, and define who EXACTLY it is for (not everyone). 3. Message: `marketing_get_messaging` - value-proposition canvas (jobs/pains/gains) + StoryBrand SB7 + the strategic narrative; build a message hierarchy. 4. Awareness stage: `marketing_get_awareness_stages` - locate the market on Schwartz's 5 stages; the more aware, the less you need to say. 5. Write: `marketing_get_copywriting_formulas` + `marketing_get_persuasion` - choose a formula (PAS/AIDA/BAB...), write headline + body + CTA, apply Cialdini triggers. 6. Brand voice: `marketing_get_voice` - choose an archetype, set the tone dimensions, apply consistently. -7. GTM: `marketing_get_gtm` (motion), `marketing_get_channels` (19 channels + Bullseye), `marketing_get_growth_model` (AARRR + the growth loop). +7. GTM: `marketing_get_gtm` (motion), `marketing_get_channels` (19 channels + Bullseye), `marketing_get_growth_model` (AARRR + loop + unit economics), `marketing_get_pricing` (value-based good-better-best packaging). 8. Check: `marketing_get_anti_patterns` - no feature-dump, we-we copy, vague unproven claims. ## On-demand mode ("give me marketing words for X") diff --git a/skills/marketing/SKILL.md b/skills/marketing/SKILL.md index be9b438..aaef640 100644 --- a/skills/marketing/SKILL.md +++ b/skills/marketing/SKILL.md @@ -20,13 +20,13 @@ Never write words before you have decided what it is, who it is for, and why it Call `marketing_brief(brand, deliverables?)` first for the ordered plan, then: -1. **Intake** - who is it for, what does it do, what are the competitive alternatives, what is unique. -2. **Position** (`marketing_get_positioning`) - Dunford's 5 components in order; write the positioning statement. +1. **Intake + VoC** - gather who/what/alternatives/unique, then `marketing_get_voice_of_customer` to mine the customer's EXACT words (reviews, support, calls). Write in their language, not yours. +2. **Position** (`marketing_get_positioning` + `marketing_get_icp`) - Dunford's 5 components in order, and define who EXACTLY it is for; write the positioning statement. 3. **Message** (`marketing_get_messaging`) - value-prop canvas (top pains/gains), StoryBrand (customer = hero), strategic narrative ("why now"); build the message hierarchy. 4. **Awareness** (`marketing_get_awareness_stages`) - locate the market on Schwartz's 5 stages; the more aware, the less you say. 5. **Write** (`marketing_get_copywriting_formulas` + `marketing_get_persuasion`) - pick a formula (PAS/AIDA/BAB), write headline + body + CTA; replace adjectives with proof. 6. **Voice** (`marketing_get_voice`) - pick ONE archetype + set the 4 tone dimensions; apply consistently. -7. **GTM** (`marketing_get_gtm`, `marketing_get_channels`, `marketing_get_growth_model`) - choose the motion, Bullseye the channels, design a growth loop. +7. **GTM** (`marketing_get_gtm`, `marketing_get_channels`, `marketing_get_growth_model`, `marketing_get_pricing`) - choose the motion, Bullseye the channels, design a growth loop, package and price on value. 8. **QA** (`marketing_get_anti_patterns`) - kill feature-dump, we-we copy, vague claims. ## On-demand mode ("give me marketing words / a tagline / a hero section for X") diff --git a/src/plugins/marketing/data.ts b/src/plugins/marketing/data.ts index 9ba21bd..0036b1e 100644 --- a/src/plugins/marketing/data.ts +++ b/src/plugins/marketing/data.ts @@ -25,6 +25,9 @@ export const GTM_DOC: string = join(snippet("gtm/motions.txt"), snippet("gtm/lau export const CHANNELS_DOC: string = snippet("gtm/channels.txt"); export const GROWTH_DOC: string = snippet("gtm/growth-model.txt"); export const ANTI_PATTERNS_DOC: string = snippet("anti-patterns/marketing.txt"); +export const VOC_DOC: string = snippet("research/voice-of-customer.txt"); +export const ICP_DOC: string = snippet("positioning/icp.txt"); +export const PRICING_DOC: string = snippet("gtm/pricing.txt"); // --- The brief assembler: deterministic, real (not a fake verdict). --- // Given which deliverables the brand needs, return the ordered workflow steps + @@ -33,23 +36,25 @@ export const ANTI_PATTERNS_DOC: string = snippet("anti-patterns/marketing.txt"); export interface BriefStep { step: string; tools: string; why: string; } export const FULL_WORKFLOW: BriefStep[] = [ - { step: "Position", tools: "marketing_get_positioning", why: "Dunford's 5 components in order - decide what it is and who it is for." }, + { step: "Research", tools: "marketing_get_voice_of_customer", why: "mine the customer's exact words (VoC) - the raw material for all the copy." }, + { step: "Position", tools: "marketing_get_positioning, marketing_get_icp", why: "Dunford's 5 components in order + define the ICP; decide what it is and who exactly it is for." }, { step: "Message", tools: "marketing_get_messaging", why: "value-prop canvas + StoryBrand + strategic narrative; build the message hierarchy." }, { step: "Awareness", tools: "marketing_get_awareness_stages", why: "match the copy to how aware the market already is." }, - { step: "Write", tools: "marketing_get_copywriting_formulas, marketing_get_persuasion", why: "pick a formula, write headline + body + CTA with proof." }, + { step: "Write", tools: "marketing_get_copywriting_formulas, marketing_get_persuasion", why: "pick a formula, write headline + body + CTA with proof - in the customer's words." }, { step: "Voice", tools: "marketing_get_voice", why: "pick a brand archetype + tone dimensions, apply consistently." }, - { step: "GTM", tools: "marketing_get_gtm, marketing_get_channels, marketing_get_growth_model", why: "choose the motion, Bullseye the channels, design a growth loop." }, + { step: "GTM", tools: "marketing_get_gtm, marketing_get_channels, marketing_get_growth_model, marketing_get_pricing", why: "choose the motion, Bullseye the channels, design a growth loop, package the pricing." }, { step: "Check", tools: "marketing_get_anti_patterns", why: "no feature-dump, we-we copy, or vague unproven claims." }, ]; const DELIVERABLE_STEPS: Record = { - positioning: ["Position"], + positioning: ["Research", "Position"], messaging: ["Message", "Awareness"], - copy: ["Awareness", "Write", "Voice"], - copywriting: ["Awareness", "Write", "Voice"], + copy: ["Research", "Awareness", "Write", "Voice"], + copywriting: ["Research", "Awareness", "Write", "Voice"], brand: ["Voice"], gtm: ["GTM"], growth: ["GTM"], + pricing: ["GTM"], }; export function buildBrief(deliverables?: string[]): BriefStep[] { diff --git a/src/plugins/marketing/index.ts b/src/plugins/marketing/index.ts index 6a84aa9..704f819 100644 --- a/src/plugins/marketing/index.ts +++ b/src/plugins/marketing/index.ts @@ -12,6 +12,9 @@ import { register as getChannels } from "./tools/get-channels.js"; import { register as getGrowthModel } from "./tools/get-growth-model.js"; import { register as getAntiPatterns } from "./tools/get-anti-patterns.js"; import { register as brief } from "./tools/brief.js"; +import { register as getVoiceOfCustomer } from "./tools/get-voice-of-customer.js"; +import { register as getIcp } from "./tools/get-icp.js"; +import { register as getPricing } from "./tools/get-pricing.js"; function register(server: McpServer): void { getPositioning(server); @@ -25,6 +28,9 @@ function register(server: McpServer): void { getChannels(server); getGrowthModel(server); getAntiPatterns(server); + getVoiceOfCustomer(server); + getIcp(server); + getPricing(server); brief(server); } diff --git a/src/plugins/marketing/snippets/gtm/growth-model.txt b/src/plugins/marketing/snippets/gtm/growth-model.txt index 8526de9..43af8ec 100644 --- a/src/plugins/marketing/snippets/gtm/growth-model.txt +++ b/src/plugins/marketing/snippets/gtm/growth-model.txt @@ -15,3 +15,10 @@ Loop types: viral (users invite users), content (UGC -> indexable pages -> SEO - Design at least one loop; loops compound, funnels do not. ## North Star metric: one number capturing delivered customer value (nights booked, messages sent, docs created). Align the team to move it. + +## Unit economics (know these cold) +- CAC = total sales + marketing spend / new customers acquired (track per channel). +- LTV = ARPA x gross margin / churn rate (or avg revenue x margin x avg lifetime). +- LTV:CAC >= 3:1 is the healthy benchmark; < 1 means you lose money on every customer. +- CAC payback < 12 months (SMB) or < 18-24 months (enterprise) is healthy. +- Retention/churn is the master variable - a small churn improvement swings LTV massively. Fix retention before scaling acquisition. diff --git a/src/plugins/marketing/snippets/gtm/pricing.txt b/src/plugins/marketing/snippets/gtm/pricing.txt new file mode 100644 index 0000000..4f3c99c --- /dev/null +++ b/src/plugins/marketing/snippets/gtm/pricing.txt @@ -0,0 +1,19 @@ +# Pricing & packaging (a marketing lever, not just finance) + +## Price on VALUE, not cost. Anchor to the outcome delivered or the cost of the alternative - never cost-plus. + +## Good-Better-Best (3 tiers) - the default packaging +- Three tiers; most buyers pick the MIDDLE. Design the middle as the plan you want sold (the target). +- A high "Best"/enterprise tier anchors perception and makes the middle look reasonable (anchoring / decoy effect). +- "Fences" between tiers = what gates the upgrade: usage (seats/volume), features, support/SLA, security/compliance. + +## Value metric: charge for the thing that scales with the value the customer gets (seats, GB, contacts, API calls, transactions). A good value metric grows revenue as the customer succeeds. + +## Tactics +- Anchor high first (show the expensive option before the cheap one). +- Annual discount for commitment; charm/odd pricing where it fits the brand. +- Free tier / trial ONLY if it drives activation and the value metric pulls users to paid (PLG). + +## Test willingness-to-pay: van Westendorp ("at what price is it too expensive / too cheap / getting expensive / a bargain?"). + +Anti-pattern: cost-plus pricing, one-size pricing, a free tier that cannibalizes instead of converting, a price the buyer cannot map to value. diff --git a/src/plugins/marketing/snippets/positioning/dunford.txt b/src/plugins/marketing/snippets/positioning/dunford.txt index fe43314..664a9d0 100644 --- a/src/plugins/marketing/snippets/positioning/dunford.txt +++ b/src/plugins/marketing/snippets/positioning/dunford.txt @@ -18,3 +18,6 @@ Choosing the category last is the key move: the category frames everything, so p ## Output: a positioning statement the whole team can repeat For [target] who [need], [product] is the [category] that [unique value], unlike [alternative], because [unique attribute]. + +## The single-minded proposition (the sharp end) +Boil it to ONE sentence the market remembers: "[Brand] is the ONLY [category] that [unique value] for [target]." If you cannot honestly say "the only...", your differentiation is not sharp enough - go back to unique attributes and cut until one is undeniable. diff --git a/src/plugins/marketing/snippets/positioning/icp.txt b/src/plugins/marketing/snippets/positioning/icp.txt new file mode 100644 index 0000000..0ec307d --- /dev/null +++ b/src/plugins/marketing/snippets/positioning/icp.txt @@ -0,0 +1,18 @@ +# Ideal Customer Profile (ICP) - who EXACTLY, not "everyone" + +The ICP is the narrow set who get the most value, buy fastest, and tell others. It deepens positioning component 4 (target). + +## Define on +- Firmographics (B2B): industry, company size, revenue, geo, stage, tech stack. +- Demographics + psychographics (B2C): role, life stage, values, identity, habits. +- Triggers: the event that makes them look NOW (new job, growth spurt, a failure, a deadline, a new regulation). +- Job-to-be-done + the pain ranked #1 for them. + +## The "who cares most" test (Dunford): rank candidate segments by who values your UNIQUE attributes the most. Start with the one that buys fast and refers. + +## Early-vangelist (Steve Blank) for new products - the best first customer: +has the problem · knows they have it · is actively looking · has cobbled together a stopgap · has budget. + +## Anti-ICP: explicitly name who you do NOT serve. A sharp "not for X" makes the "for Y" credible and the message louder. + +Rule: niche down until the message feels almost too specific - that is when it converts. "For everyone" lands with no one. diff --git a/src/plugins/marketing/snippets/research/voice-of-customer.txt b/src/plugins/marketing/snippets/research/voice-of-customer.txt new file mode 100644 index 0000000..bd0e8df --- /dev/null +++ b/src/plugins/marketing/snippets/research/voice-of-customer.txt @@ -0,0 +1,23 @@ +# Voice of Customer (VoC) & message mining (Joanna Wiebe / Copyhackers) +# This is HOW you get marketing words straight from the customer - the highest-leverage copy method. + +## Rule: do not write copy from your head. STEAL the customer's own words. + +## Mine these sources for raw language +- Product reviews - yours AND competitors'. 3-star reviews are the most honest. (G2, Amazon, App Store, Trustpilot, Capterra.) +- Support tickets, chat logs, sales-call recordings, win/loss interviews. +- Survey open-ends: "what almost stopped you from buying?", "what's your #1 frustration with [job]?", "describe the moment you realized you needed this". +- Communities: Reddit, forums, Discord, social replies, review-site Q&A. + +## What to pull (tag each verbatim quote) +- Desired outcomes / jobs (what they're trying to get done). +- Pains - in their EXACT phrasing of the frustration. +- Objections / hesitations (what almost stopped them). +- The moment of value ("I finally...", "now I can...", "it just..."). + +## Turn quotes into copy +- Use their exact phrases as headlines/subheads. Customers convert on THEIR language, not your jargon. +- The "so what?" / "and that means..." test: take a feature, ask "so what for the customer?" until you reach the outcome they actually named. +- Build a swipe file of the strongest verbatim lines; assemble the page from real quotes. + +Anti-pattern: aspirational brand language no real customer has ever said. If a customer wouldn't say it out loud, cut it. diff --git a/src/plugins/marketing/tools/get-icp.ts b/src/plugins/marketing/tools/get-icp.ts new file mode 100644 index 0000000..331c3a8 --- /dev/null +++ b/src/plugins/marketing/tools/get-icp.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { ICP_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "marketing_get_icp", + "Define the Ideal Customer Profile: firmographics/psychographics, buying triggers, the 'who cares most' test, the early-vangelist profile, and the anti-ICP. Use during positioning to decide who EXACTLY it is for - not 'everyone'.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Ideal Customer Profile\n\n${ICP_DOC}\n` }] }), + ); +} diff --git a/src/plugins/marketing/tools/get-pricing.ts b/src/plugins/marketing/tools/get-pricing.ts new file mode 100644 index 0000000..33dfe49 --- /dev/null +++ b/src/plugins/marketing/tools/get-pricing.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { PRICING_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "marketing_get_pricing", + "Pricing & packaging as a marketing lever: value-based pricing, good-better-best tiering, anchoring, fences between tiers, the value metric, and willingness-to-pay testing. Use to package and price the offer.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Pricing & packaging\n\n${PRICING_DOC}\n` }] }), + ); +} diff --git a/src/plugins/marketing/tools/get-voice-of-customer.ts b/src/plugins/marketing/tools/get-voice-of-customer.ts new file mode 100644 index 0000000..f456227 --- /dev/null +++ b/src/plugins/marketing/tools/get-voice-of-customer.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { VOC_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "marketing_get_voice_of_customer", + "Voice-of-Customer / message mining (Copyhackers): how to harvest the customer's EXACT words from reviews, support, calls, and surveys, and turn them into copy. The method for getting marketing words straight from real customers. Use FIRST, before writing anything.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Voice of Customer\n\n${VOC_DOC}\n` }] }), + ); +} diff --git a/tests/marketing-behaviour.test.ts b/tests/marketing-behaviour.test.ts index b69a930..e3bf48b 100644 --- a/tests/marketing-behaviour.test.ts +++ b/tests/marketing-behaviour.test.ts @@ -5,10 +5,12 @@ import { register as getPositioning } from "../src/plugins/marketing/tools/get-p import { register as getFormulas } from "../src/plugins/marketing/tools/get-copywriting-formulas.ts"; import { register as getChannels } from "../src/plugins/marketing/tools/get-channels.ts"; import { register as brief } from "../src/plugins/marketing/tools/brief.ts"; +import { register as getVoc } from "../src/plugins/marketing/tools/get-voice-of-customer.ts"; // --- the brief assembler --- -test("full workflow leads with positioning", () => { - expect(FULL_WORKFLOW[0].step).toBe("Position"); +test("full workflow leads with VoC research, then positioning", () => { + expect(FULL_WORKFLOW[0].step).toBe("Research"); + expect(FULL_WORKFLOW.map((s) => s.step)).toContain("Position"); }); test("buildBrief returns the full workflow when no deliverables given", () => { @@ -52,3 +54,10 @@ test("brief assembles an ordered plan for a specific brand", async () => { expect(text).toMatch(/meditation app/); expect(text).toMatch(/Position/); }); + +test("get_voice_of_customer returns the message-mining method", async () => { + const tool = captureTool(getVoc); + expect(tool.name).toBe("marketing_get_voice_of_customer"); + const text = extractTextContent(await tool.invoke({})); + expect(text).toMatch(/exact words|verbatim|reviews/i); +}); From b1bea897333ff6d13c9de7a9bc9d0d450705d4c3 Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Mon, 22 Jun 2026 01:47:57 +0530 Subject: [PATCH 13/16] feat(marketing): add hooks, lifecycle flows, Blue Ocean differentiation (web-sourced) From inline web research (GitHub skill repos are coding-focused, thin on marketing): - Hooks: first-3-seconds frameworks (curiosity/open-loop, pain, pattern-interrupt, contrarian, result) + the 4-beat short-form structure + swipe openers. New tool. - Lifecycle/email flows: welcome -> onboarding -> nurture -> retention -> win-back, behavior-triggered (the retention + monetization engine). New tool. - Blue Ocean differentiation: ERRC grid (eliminate/reduce/raise/create) + value innovation, folded into positioning. 17 tools, 25 snippets. bun test 73 pass, tsc clean. --- src/plugins/marketing/data.ts | 8 ++++--- src/plugins/marketing/index.ts | 4 ++++ .../marketing/snippets/copywriting/hooks.txt | 24 +++++++++++++++++++ .../marketing/snippets/gtm/lifecycle.txt | 16 +++++++++++++ .../snippets/positioning/differentiation.txt | 13 ++++++++++ src/plugins/marketing/tools/get-hooks.ts | 11 +++++++++ src/plugins/marketing/tools/get-lifecycle.ts | 11 +++++++++ 7 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 src/plugins/marketing/snippets/copywriting/hooks.txt create mode 100644 src/plugins/marketing/snippets/gtm/lifecycle.txt create mode 100644 src/plugins/marketing/snippets/positioning/differentiation.txt create mode 100644 src/plugins/marketing/tools/get-hooks.ts create mode 100644 src/plugins/marketing/tools/get-lifecycle.ts diff --git a/src/plugins/marketing/data.ts b/src/plugins/marketing/data.ts index 0036b1e..795fbe8 100644 --- a/src/plugins/marketing/data.ts +++ b/src/plugins/marketing/data.ts @@ -14,7 +14,7 @@ import { snippet } from "./loader.js"; const join = (...parts: string[]) => parts.join("\n\n---\n\n"); -export const POSITIONING_DOC: string = join(snippet("positioning/dunford.txt"), snippet("positioning/styles.txt")); +export const POSITIONING_DOC: string = join(snippet("positioning/dunford.txt"), snippet("positioning/styles.txt"), snippet("positioning/differentiation.txt")); export const MESSAGING_DOC: string = join(snippet("messaging/value-prop-canvas.txt"), snippet("messaging/storybrand.txt"), snippet("messaging/narrative.txt")); export const FORMULAS_DOC: string = snippet("copywriting/formulas.txt"); export const AWARENESS_DOC: string = snippet("copywriting/awareness-stages.txt"); @@ -28,6 +28,8 @@ export const ANTI_PATTERNS_DOC: string = snippet("anti-patterns/marketing.txt"); export const VOC_DOC: string = snippet("research/voice-of-customer.txt"); export const ICP_DOC: string = snippet("positioning/icp.txt"); export const PRICING_DOC: string = snippet("gtm/pricing.txt"); +export const HOOKS_DOC: string = snippet("copywriting/hooks.txt"); +export const LIFECYCLE_DOC: string = snippet("gtm/lifecycle.txt"); // --- The brief assembler: deterministic, real (not a fake verdict). --- // Given which deliverables the brand needs, return the ordered workflow steps + @@ -40,9 +42,9 @@ export const FULL_WORKFLOW: BriefStep[] = [ { step: "Position", tools: "marketing_get_positioning, marketing_get_icp", why: "Dunford's 5 components in order + define the ICP; decide what it is and who exactly it is for." }, { step: "Message", tools: "marketing_get_messaging", why: "value-prop canvas + StoryBrand + strategic narrative; build the message hierarchy." }, { step: "Awareness", tools: "marketing_get_awareness_stages", why: "match the copy to how aware the market already is." }, - { step: "Write", tools: "marketing_get_copywriting_formulas, marketing_get_persuasion", why: "pick a formula, write headline + body + CTA with proof - in the customer's words." }, + { step: "Write", tools: "marketing_get_copywriting_formulas, marketing_get_hooks, marketing_get_persuasion", why: "hook the first 3 seconds, pick a formula, write headline + body + CTA with proof - in the customer's words." }, { step: "Voice", tools: "marketing_get_voice", why: "pick a brand archetype + tone dimensions, apply consistently." }, - { step: "GTM", tools: "marketing_get_gtm, marketing_get_channels, marketing_get_growth_model, marketing_get_pricing", why: "choose the motion, Bullseye the channels, design a growth loop, package the pricing." }, + { step: "GTM", tools: "marketing_get_gtm, marketing_get_channels, marketing_get_growth_model, marketing_get_lifecycle, marketing_get_pricing", why: "choose the motion, Bullseye the channels, design a growth loop, set up lifecycle flows, package the pricing." }, { step: "Check", tools: "marketing_get_anti_patterns", why: "no feature-dump, we-we copy, or vague unproven claims." }, ]; diff --git a/src/plugins/marketing/index.ts b/src/plugins/marketing/index.ts index 704f819..f15fb14 100644 --- a/src/plugins/marketing/index.ts +++ b/src/plugins/marketing/index.ts @@ -15,6 +15,8 @@ import { register as brief } from "./tools/brief.js"; import { register as getVoiceOfCustomer } from "./tools/get-voice-of-customer.js"; import { register as getIcp } from "./tools/get-icp.js"; import { register as getPricing } from "./tools/get-pricing.js"; +import { register as getHooks } from "./tools/get-hooks.js"; +import { register as getLifecycle } from "./tools/get-lifecycle.js"; function register(server: McpServer): void { getPositioning(server); @@ -31,6 +33,8 @@ function register(server: McpServer): void { getVoiceOfCustomer(server); getIcp(server); getPricing(server); + getHooks(server); + getLifecycle(server); brief(server); } diff --git a/src/plugins/marketing/snippets/copywriting/hooks.txt b/src/plugins/marketing/snippets/copywriting/hooks.txt new file mode 100644 index 0000000..98d99e5 --- /dev/null +++ b/src/plugins/marketing/snippets/copywriting/hooks.txt @@ -0,0 +1,24 @@ +# Hooks - the first 1-3 seconds (ads, short-form video, social, email subject, opening line) +# Rule: if the hook does not land in 2-3 seconds, they scroll. The hook's only job is to buy the next 3 seconds. + +## Hook frameworks +- Curiosity / open loop: pose a question or tease a payoff, delay the answer (open loop -> tease payoff -> withhold the explanation). +- Pain-point: name the audience's exact frustration in the first line. "Still [painful thing]?" +- Pattern interrupt: do or say something unexpected that breaks the scroll autopilot (visual or claim). +- Contrarian / myth-bust: "Everyone tells you X. They're wrong." +- Result / proof: lead with the outcome + specifics. "How I [result] in [time] without [pain]." +- Step-by-step / listicle: "3 ways to [outcome]" - structured, low-overwhelm. +- Warning / negative: "Stop doing X if you want Y." + +## Short-form video / ad structure (the 4 beats) +1. Hook (0-3s) - a clear, specific promise + striking first frame + legible on-screen text. Use an outcome/number/contrast, not a vague topic. +2. Value drop (4-15s) - deliver on the hook fast. +3. Story / payoff (16-45s) - proof, demo, transformation. +4. CTA (last ~5s) - one clear action. + +## Swipe openers +- "If you [audience], stop scrolling." +- "I tried [thing] for [time]. Here is what happened." +- "Nobody talks about [surprising truth]." +- "[Number] [things] that [outcome] - the last one is [twist]." +- "POV: you [relatable situation]." diff --git a/src/plugins/marketing/snippets/gtm/lifecycle.txt b/src/plugins/marketing/snippets/gtm/lifecycle.txt new file mode 100644 index 0000000..cee42f4 --- /dev/null +++ b/src/plugins/marketing/snippets/gtm/lifecycle.txt @@ -0,0 +1,16 @@ +# Lifecycle / email marketing (the retention + monetization engine) +# Trigger on BEHAVIOR (signups, feature use, drop-off), not on a blast calendar. Automated flows make roughly 3x the revenue of one-off campaigns. + +## The core flows (build in this order) +1. Welcome (on signup) - set expectations, one clear next action, point at the "aha". Highest-converting automated email. +2. Onboarding (first days) - educate toward the activation moment, do not sell. One feature/step per email; triggered by what they have and have not done. +3. Nurture (pre-purchase / free users) - teach, give value, build the case; move them to the next stage. +4. Engagement / retention - prompt the next valuable action, celebrate milestones, reactivate before they churn. +5. Win-back (gone quiet) - "we miss you" + a real reason to return (new value or offer); cheaper than acquiring new. +6. Transactional / behavioral - receipts, alerts, milestones, abandoned-cart (high intent, high revenue). + +## Rules +- Right message, right moment: real-time behavioral triggers beat scheduled blasts. +- Fewer, more relevant beats frequent + generic. Segment by stage and behavior. +- One job per email, one primary CTA. +- Measure to the value metric (activation, repeat purchase, retention), not opens. diff --git a/src/plugins/marketing/snippets/positioning/differentiation.txt b/src/plugins/marketing/snippets/positioning/differentiation.txt new file mode 100644 index 0000000..2860fed --- /dev/null +++ b/src/plugins/marketing/snippets/positioning/differentiation.txt @@ -0,0 +1,13 @@ +# Differentiation: Blue Ocean - compete by being different, not by out-spending (Kim & Mauborgne) + +Value innovation = pursue differentiation AND low cost at once by redrawing the market, instead of beating rivals on the same factors. + +## Four Actions / ERRC grid - rebuild the value curve by asking: +- ELIMINATE - which factors the industry takes for granted should be removed entirely? +- REDUCE - which factors should be cut well below the industry standard? +- RAISE - which factors should be raised well above the standard? +- CREATE - which factors should you offer that the industry never has? + +Example ([yellow tail] / Casella wine): eliminated wine jargon and aging prestige, reduced the range, raised approachability, created "easy, fun, adventurous" - winning non-wine-drinkers, a whole new buyer. + +Use it to find an uncontested angle for positioning: the CREATE and ELIMINATE rows are where the differentiated story lives. Pairs with Dunford (the attributes you raise/create become your unique value) and category design (a new value curve can justify a new category). diff --git a/src/plugins/marketing/tools/get-hooks.ts b/src/plugins/marketing/tools/get-hooks.ts new file mode 100644 index 0000000..9f15bc0 --- /dev/null +++ b/src/plugins/marketing/tools/get-hooks.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { HOOKS_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "marketing_get_hooks", + "Hook frameworks for the first 1-3 seconds (ads, short-form video, social, subject lines): curiosity/open-loop, pain-point, pattern-interrupt, contrarian, result, plus the 4-beat short-form structure and swipe openers. Use to grab attention before the scroll.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Hooks\n\n${HOOKS_DOC}\n` }] }), + ); +} diff --git a/src/plugins/marketing/tools/get-lifecycle.ts b/src/plugins/marketing/tools/get-lifecycle.ts new file mode 100644 index 0000000..69fa3d9 --- /dev/null +++ b/src/plugins/marketing/tools/get-lifecycle.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { LIFECYCLE_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "marketing_get_lifecycle", + "Lifecycle / email marketing flows: welcome, onboarding, nurture, engagement/retention, win-back, and behavioral/transactional - triggered on behavior, not a blast calendar. Use to design the retention and monetization engine after acquisition.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Lifecycle marketing\n\n${LIFECYCLE_DOC}\n` }] }), + ); +} From 45d6eaa78e949017f3a68ed12c97a3414f36114f Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Mon, 22 Jun 2026 01:52:55 +0530 Subject: [PATCH 14/16] feat(product-manager): add prioritization, decisions, MVP, roadmap, metrics Find-more sweep (web; GitHub is coding-skill focused, thin on PM frameworks). Closes 3 of the 4 spec-flagged gaps: - Prioritization: MoSCoW, Kano, ICE, WSJF/cost-of-delay, opportunity scoring (gap #2). - MVP & scope-cutting: RAT, vertical slicing, walking skeleton, the cut test (gap #1). - Decision toolkit: Type-1/2 (reversible), 70% rule, pre-mortem, disagree-and-commit, decision records, decision-vs-outcome quality (gap #4). - Plus roadmapping (now/next/later, outcome-based, GIST) and metrics (NSM, OKR, HEART, AARRR). 5 new tools (14 total), 5 new snippets. bun test 76 pass, tsc clean. --- src/plugins/product-manager/data.ts | 5 ++++ src/plugins/product-manager/index.ts | 10 ++++++++ .../snippets/decisions/toolkit.txt | 16 +++++++++++++ .../snippets/metrics/metrics.txt | 15 ++++++++++++ .../snippets/prioritization/frameworks.txt | 21 ++++++++++++++++ .../snippets/roadmap/roadmap.txt | 15 ++++++++++++ .../product-manager/snippets/scope/mvp.txt | 15 ++++++++++++ .../tools/get-decision-tools.ts | 11 +++++++++ .../product-manager/tools/get-metrics.ts | 11 +++++++++ .../product-manager/tools/get-mvp-scoping.ts | 11 +++++++++ .../tools/get-prioritization.ts | 11 +++++++++ .../product-manager/tools/get-roadmapping.ts | 11 +++++++++ tests/product-manager-behaviour.test.ts | 24 +++++++++++++++++++ 13 files changed, 176 insertions(+) create mode 100644 src/plugins/product-manager/snippets/decisions/toolkit.txt create mode 100644 src/plugins/product-manager/snippets/metrics/metrics.txt create mode 100644 src/plugins/product-manager/snippets/prioritization/frameworks.txt create mode 100644 src/plugins/product-manager/snippets/roadmap/roadmap.txt create mode 100644 src/plugins/product-manager/snippets/scope/mvp.txt create mode 100644 src/plugins/product-manager/tools/get-decision-tools.ts create mode 100644 src/plugins/product-manager/tools/get-metrics.ts create mode 100644 src/plugins/product-manager/tools/get-mvp-scoping.ts create mode 100644 src/plugins/product-manager/tools/get-prioritization.ts create mode 100644 src/plugins/product-manager/tools/get-roadmapping.ts diff --git a/src/plugins/product-manager/data.ts b/src/plugins/product-manager/data.ts index 9a19d8b..d6f65e8 100644 --- a/src/plugins/product-manager/data.ts +++ b/src/plugins/product-manager/data.ts @@ -57,6 +57,11 @@ export const JTBD_DOC: string = snippet("jtbd/jtbd.txt"); // Rubrics the AGENT applies (ground truth, not a verdict the tool computes). export const OPPORTUNITY_RUBRIC_DOC: string = snippet("rubrics/opportunity-vs-solution.txt"); export const JOB_CRITERIA_DOC: string = snippet("rubrics/job-criteria.txt"); +export const PRIORITIZATION_DOC: string = snippet("prioritization/frameworks.txt"); +export const DECISION_TOOLKIT_DOC: string = snippet("decisions/toolkit.txt"); +export const MVP_DOC: string = snippet("scope/mvp.txt"); +export const ROADMAP_DOC: string = snippet("roadmap/roadmap.txt"); +export const METRICS_DOC: string = snippet("metrics/metrics.txt"); // --- The one legitimate piece of logic: a deterministic STRUCTURE gate. --- // This does NOT judge whether the value/viability reasoning is good (that is the diff --git a/src/plugins/product-manager/index.ts b/src/plugins/product-manager/index.ts index c31f30f..cc4db4b 100644 --- a/src/plugins/product-manager/index.ts +++ b/src/plugins/product-manager/index.ts @@ -9,6 +9,11 @@ import { register as opportunityVsSolution } from "./tools/opportunity-vs-soluti import { register as validateJobStatement } from "./tools/validate-job-statement.js"; import { register as scoreRice } from "./tools/score-rice.js"; import { register as resolveProductDecision } from "./tools/resolve-product-decision.js"; +import { register as getPrioritization } from "./tools/get-prioritization.js"; +import { register as getDecisionTools } from "./tools/get-decision-tools.js"; +import { register as getMvpScoping } from "./tools/get-mvp-scoping.js"; +import { register as getRoadmapping } from "./tools/get-roadmapping.js"; +import { register as getMetrics } from "./tools/get-metrics.js"; function register(server: McpServer): void { getFourRisks(server); @@ -20,6 +25,11 @@ function register(server: McpServer): void { validateJobStatement(server); scoreRice(server); resolveProductDecision(server); + getPrioritization(server); + getDecisionTools(server); + getMvpScoping(server); + getRoadmapping(server); + getMetrics(server); } export const productManagerPlugin: Plugin = { diff --git a/src/plugins/product-manager/snippets/decisions/toolkit.txt b/src/plugins/product-manager/snippets/decisions/toolkit.txt new file mode 100644 index 0000000..253a35d --- /dev/null +++ b/src/plugins/product-manager/snippets/decisions/toolkit.txt @@ -0,0 +1,16 @@ +# Decision-making toolkit (make better calls under uncertainty) + +## Type 1 vs Type 2 decisions (Bezos) - match speed to reversibility +- Type 1 / one-way door: hard or impossible to reverse. Decide carefully, more data, senior eyes. +- Type 2 / two-way door: easily reversible. Decide FAST, delegate, do not agonize - you can undo it. +Anti-pattern: treating reversible (Type 2) decisions as if they were irreversible -> slow, over-analyzed. Most product decisions are Type 2. + +## The 70% rule (Bezos): decide with ~70% of the info you wish you had. Waiting for 90% is too slow. Be good at being wrong fast and course-correcting. + +## Pre-mortem (Gary Klein, via Kahneman): BEFORE committing, imagine it is months later and the project FAILED. Everyone writes why. Surfaces the blind spots and failure modes optimism hides. Run it on every significant build. + +## Disagree and commit: once the call is made, even dissenters commit fully. State the disagreement, then row in the same direction. Kills passive resistance and endless re-litigation. + +## Decision record (log it): for any non-trivial call write down the decision, date, options considered, the reasoning, the assumptions, and what would make you reverse it. Cheap to write; kills hindsight bias and re-arguing later. + +## Decision quality != outcome quality: a good decision (good process + best available info) can have a bad outcome, and vice versa. Judge the process. For repeated bets, use expected value (probability x payoff), not gut. diff --git a/src/plugins/product-manager/snippets/metrics/metrics.txt b/src/plugins/product-manager/snippets/metrics/metrics.txt new file mode 100644 index 0000000..c7c4904 --- /dev/null +++ b/src/plugins/product-manager/snippets/metrics/metrics.txt @@ -0,0 +1,15 @@ +# Product metrics - measure outcomes, not output + +## North Star Metric (NSM): the ONE metric that best captures the value customers get (nights booked, messages sent, weekly active teams). A leading indicator of sustainable growth - align the org to move it, and pair with 2-4 input metrics that drive it. + +## One Metric That Matters (OMTM): at any stage, focus the team on the single metric that matters most right now (it changes by stage and goal). + +## OKRs: Objective (qualitative, ambitious, time-boxed) + 2-4 Key Results (measurable OUTCOMES, not tasks). A KR is a result ("activation 20% -> 35%"), never a to-do ("ship feature X"). + +## HEART (Google) - UX quality: Happiness, Engagement, Adoption, Retention, Task success. For each, define Goals -> Signals -> Metrics. + +## AARRR pirate funnel (acquisition / activation / retention / referral / revenue) - find the leakiest stage. (see marketing_get_growth_model) + +## Input vs output (leading vs lagging): optimize the INPUT metrics you control (the levers); watch OUTPUT metrics (revenue) as results. Avoid vanity metrics - totals that only go up and do not tie to value. + +## Rule: a metric without a target and an owner is decoration. Instrument activation and retention before scaling acquisition. diff --git a/src/plugins/product-manager/snippets/prioritization/frameworks.txt b/src/plugins/product-manager/snippets/prioritization/frameworks.txt new file mode 100644 index 0000000..53a270f --- /dev/null +++ b/src/plugins/product-manager/snippets/prioritization/frameworks.txt @@ -0,0 +1,21 @@ +# Prioritization frameworks - pick the one that fits the decision. (RICE is the default scorer; here are the rest.) + +## MoSCoW - scope a release +Must have (non-negotiable, it breaks without it) / Should have (high value, not blocking) / Could have (nice, first to drop under pressure) / Won't have (explicitly out, this release). Forces the "Won't" - the discipline of saying no out loud. + +## Kano model (Noriaki Kano) - classify features by how they drive satisfaction +- Basic / must-be: expected; absent = anger, present = neutral. Table stakes. +- Performance / linear: more is better; satisfaction scales with how well you do it. +- Excitement / delighters: unexpected; present = delight, absent = no harm. The differentiators. +- Indifferent: nobody cares - cut it. +- Reverse: its presence actively annoys some users. +Rule: cover all Basics, compete on Performance, win on a few Excitement. Delighters decay into Basics over time. + +## ICE = Impact x Confidence x Ease. Fast and rough; good for experiment/growth backlogs (highest-learning, lowest-effort first). + +## WSJF (Weighted Shortest Job First, SAFe) = Cost of Delay / Job Duration. Do high-cost-of-delay short jobs first. +- Cost of Delay = user/business value + time criticality + risk-reduction / opportunity-enablement. + +## Opportunity scoring (Ulwick / ODI) - find gaps where IMPORTANCE is high and SATISFACTION is low. That gap is the underserved opportunity. Score ~ importance + (importance - satisfaction). + +## Rule: prioritize against a desired OUTCOME, not a feature wish-list. Prioritization is the act of saying no to good ideas. diff --git a/src/plugins/product-manager/snippets/roadmap/roadmap.txt b/src/plugins/product-manager/snippets/roadmap/roadmap.txt new file mode 100644 index 0000000..f98d9fe --- /dev/null +++ b/src/plugins/product-manager/snippets/roadmap/roadmap.txt @@ -0,0 +1,15 @@ +# Roadmapping - communicate direction without faking precision + +## Now / Next / Later - the default modern roadmap. Three horizons of decreasing certainty: +- Now: committed, in progress (specific). +- Next: likely next, shaped but not committed (themes). +- Later: directional bets (problems / outcomes, not features). +Shows direction without false-precision date promises. + +## Outcome-based, not feature-based (Cagan): give teams a prioritized set of PROBLEMS / OUTCOMES to solve, not a dated feature list. A feature roadmap with deadlines is the feature-factory anti-pattern. + +## Theme-based: organize around customer problems / strategic themes, each tied to an outcome metric. + +## GIST (Itamar Gilad): Goals -> Ideas -> Steps -> Tasks. Goals are outcomes; ideas are many and cheap; step-projects test them; tasks execute. Decouples stable direction from disposable features. + +## Rules: tie every roadmap item to a goal/outcome; the further out, the fuzzier (problems, not features); revisit continuously - the roadmap is a living bet, not a contract. diff --git a/src/plugins/product-manager/snippets/scope/mvp.txt b/src/plugins/product-manager/snippets/scope/mvp.txt new file mode 100644 index 0000000..d4aafa0 --- /dev/null +++ b/src/plugins/product-manager/snippets/scope/mvp.txt @@ -0,0 +1,15 @@ +# MVP & scope-cutting - ship the smallest thing that creates and captures value + +## MVP = the smallest version that lets you LEARN the riskiest thing (or deliver the core value) - not a tiny pile of features, not a broken half-product. "Viable" matters as much as "minimal". + +## Riskiest Assumption Test (RAT): instead of building the whole MVP, find the assumption that, if wrong, kills the idea, and test THAT first with the cheapest experiment that de-risks it. Often a landing page, a concierge / Wizard-of-Oz, or 5 customer tests - no code. + +## Slice vertically, not horizontally: ship a thin end-to-end "walking skeleton" (one real user does one real job start to finish), then thicken. Never ship a horizontal layer nobody can use yet. + +## Story slicing - cut a big story to the thinnest valuable slice: by workflow step, by user type, happy-path-first (defer edge cases), manual-before-automated, one-platform-first. + +## The cut test: for every item in scope ask "if we remove this, does the core value still work?" If yes, cut it from v1. Default to cutting. + +## De-risk order: value risk first (do they want it?), then usability, then feasibility. Do not perfect the build of something nobody wants. + +Anti-pattern: gold-plating v1, building all edge cases before validating the core, an "MVP" that is secretly a full product. diff --git a/src/plugins/product-manager/tools/get-decision-tools.ts b/src/plugins/product-manager/tools/get-decision-tools.ts new file mode 100644 index 0000000..010ae31 --- /dev/null +++ b/src/plugins/product-manager/tools/get-decision-tools.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { DECISION_TOOLKIT_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_get_decision_tools", + "Decision-making toolkit: Type-1/Type-2 (reversible vs one-way-door) decisions, the 70% rule, pre-mortems, disagree-and-commit, decision records, and decision-vs-outcome quality. Use to make and document calls under uncertainty.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Decision toolkit\n\n${DECISION_TOOLKIT_DOC}\n` }] }), + ); +} diff --git a/src/plugins/product-manager/tools/get-metrics.ts b/src/plugins/product-manager/tools/get-metrics.ts new file mode 100644 index 0000000..cbd0601 --- /dev/null +++ b/src/plugins/product-manager/tools/get-metrics.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { METRICS_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_get_metrics", + "Product metrics: North Star metric, One Metric That Matters, OKRs (outcomes not tasks), Google's HEART, AARRR, and input-vs-output / vanity-metric traps. Use to define what success is measured by.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Product metrics\n\n${METRICS_DOC}\n` }] }), + ); +} diff --git a/src/plugins/product-manager/tools/get-mvp-scoping.ts b/src/plugins/product-manager/tools/get-mvp-scoping.ts new file mode 100644 index 0000000..10f613f --- /dev/null +++ b/src/plugins/product-manager/tools/get-mvp-scoping.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { MVP_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_get_mvp_scoping", + "MVP & scope-cutting: what an MVP really is, the Riskiest Assumption Test, vertical slicing / walking skeleton, story slicing, the cut test, and de-risk order (value->usability->feasibility). Use to cut a build to its thinnest valuable slice.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# MVP & scope-cutting\n\n${MVP_DOC}\n` }] }), + ); +} diff --git a/src/plugins/product-manager/tools/get-prioritization.ts b/src/plugins/product-manager/tools/get-prioritization.ts new file mode 100644 index 0000000..5d28b69 --- /dev/null +++ b/src/plugins/product-manager/tools/get-prioritization.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { PRIORITIZATION_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_get_prioritization", + "Prioritization frameworks beyond RICE: MoSCoW (release scoping), Kano (basic/performance/excitement), ICE, WSJF / cost-of-delay, and opportunity scoring (importance vs satisfaction gap). Use to decide what to build and in what order.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Prioritization\n\n${PRIORITIZATION_DOC}\n` }] }), + ); +} diff --git a/src/plugins/product-manager/tools/get-roadmapping.ts b/src/plugins/product-manager/tools/get-roadmapping.ts new file mode 100644 index 0000000..5c503e4 --- /dev/null +++ b/src/plugins/product-manager/tools/get-roadmapping.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { ROADMAP_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_get_roadmapping", + "Roadmapping: Now/Next/Later horizons, outcome-based (not feature-based) roadmaps, theme-based organization, and GIST. Use to communicate direction without faking date precision or sliding into a feature factory.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Roadmapping\n\n${ROADMAP_DOC}\n` }] }), + ); +} diff --git a/tests/product-manager-behaviour.test.ts b/tests/product-manager-behaviour.test.ts index f9b39e5..f5f1a32 100644 --- a/tests/product-manager-behaviour.test.ts +++ b/tests/product-manager-behaviour.test.ts @@ -12,6 +12,9 @@ import { register as oppVsSol } from "../src/plugins/product-manager/tools/oppor import { register as validateJob } from "../src/plugins/product-manager/tools/validate-job-statement.ts"; import { register as scoreRiceTool } from "../src/plugins/product-manager/tools/score-rice.ts"; import { register as resolveDecision } from "../src/plugins/product-manager/tools/resolve-product-decision.ts"; +import { register as getPrioritization } from "../src/plugins/product-manager/tools/get-prioritization.ts"; +import { register as getDecisionTools } from "../src/plugins/product-manager/tools/get-decision-tools.ts"; +import { register as getMvpScoping } from "../src/plugins/product-manager/tools/get-mvp-scoping.ts"; // --- data layer --- test("four risks are exactly the canonical four", () => { @@ -109,3 +112,24 @@ test("resolve_product_decision PASSes when value and viability are supplied", as })); expect(text).toMatch(/PASS/); }); + +test("get_prioritization includes Kano and MoSCoW", async () => { + const tool = captureTool(getPrioritization); + expect(tool.name).toBe("product_manager_get_prioritization"); + const text = extractTextContent(await tool.invoke({})); + expect(text).toMatch(/Kano/); + expect(text).toMatch(/MoSCoW/); +}); + +test("get_decision_tools includes Type 1/Type 2 and pre-mortem", async () => { + const tool = captureTool(getDecisionTools); + const text = extractTextContent(await tool.invoke({})); + expect(text).toMatch(/Type 1/); + expect(text).toMatch(/[Pp]re-mortem/); +}); + +test("get_mvp_scoping includes the riskiest assumption test", async () => { + const tool = captureTool(getMvpScoping); + const text = extractTextContent(await tool.invoke({})); + expect(text).toMatch(/Riskiest Assumption|riskiest assumption/); +}); From ca6106b5e6a788209347f710decd888e05fa701d Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Mon, 22 Jun 2026 01:57:24 +0530 Subject: [PATCH 15/16] feat(product-manager): add product-sense development tool (closes spec gap #3) How product sense is actually built (Jules Walter, Julie Zhuo): customer immersion, product teardowns/critiques, principles-first, closing the predict/outcome loop. 15th PM tool. All 4 spec-flagged gaps now closed. bun test pass, tsc clean. --- src/plugins/product-manager/data.ts | 1 + src/plugins/product-manager/index.ts | 2 ++ .../snippets/product-sense/develop.txt | 22 +++++++++++++++++++ .../tools/get-product-sense.ts | 11 ++++++++++ tests/product-manager-behaviour.test.ts | 9 ++++++++ 5 files changed, 45 insertions(+) create mode 100644 src/plugins/product-manager/snippets/product-sense/develop.txt create mode 100644 src/plugins/product-manager/tools/get-product-sense.ts diff --git a/src/plugins/product-manager/data.ts b/src/plugins/product-manager/data.ts index d6f65e8..283be66 100644 --- a/src/plugins/product-manager/data.ts +++ b/src/plugins/product-manager/data.ts @@ -62,6 +62,7 @@ export const DECISION_TOOLKIT_DOC: string = snippet("decisions/toolkit.txt"); export const MVP_DOC: string = snippet("scope/mvp.txt"); export const ROADMAP_DOC: string = snippet("roadmap/roadmap.txt"); export const METRICS_DOC: string = snippet("metrics/metrics.txt"); +export const PRODUCT_SENSE_DOC: string = snippet("product-sense/develop.txt"); // --- The one legitimate piece of logic: a deterministic STRUCTURE gate. --- // This does NOT judge whether the value/viability reasoning is good (that is the diff --git a/src/plugins/product-manager/index.ts b/src/plugins/product-manager/index.ts index cc4db4b..ef93c76 100644 --- a/src/plugins/product-manager/index.ts +++ b/src/plugins/product-manager/index.ts @@ -14,6 +14,7 @@ import { register as getDecisionTools } from "./tools/get-decision-tools.js"; import { register as getMvpScoping } from "./tools/get-mvp-scoping.js"; import { register as getRoadmapping } from "./tools/get-roadmapping.js"; import { register as getMetrics } from "./tools/get-metrics.js"; +import { register as getProductSense } from "./tools/get-product-sense.js"; function register(server: McpServer): void { getFourRisks(server); @@ -30,6 +31,7 @@ function register(server: McpServer): void { getMvpScoping(server); getRoadmapping(server); getMetrics(server); + getProductSense(server); } export const productManagerPlugin: Plugin = { diff --git a/src/plugins/product-manager/snippets/product-sense/develop.txt b/src/plugins/product-manager/snippets/product-sense/develop.txt new file mode 100644 index 0000000..2922def --- /dev/null +++ b/src/plugins/product-manager/snippets/product-sense/develop.txt @@ -0,0 +1,22 @@ +# How to develop product sense (Jules Walter, Julie Zhuo) - a learned skill, not a gift. Deliberate practice. + +## 1. Customer immersion (the foundation) +- Talk to users regularly: scheduled interviews, usability tests, read support tickets. Ask open-ended questions; watch body language and emotion. +- Shadow users at work: sit beside a real customer doing a real task (yours or a competitor's). You see what surveys miss - environment, interruptions, workarounds, emotional cues. +- Do support and sales calls yourself. Empathy is raw data. + +## 2. Product teardowns / critiques (build pattern recognition) +- Spend 1-2 hours a month trying new products and deconstructing them: why does this work, why does that not? +- Use Julie Zhuo's critique questions: what is the signup / getting-started experience? what is the core flow? what would I change and why? +- Compare 3+ products in the SAME category - how does each solve the same job differently? Keep a written pattern library. + +## 3. Principles-first, not technique-copying (Cagan) +- Master enduring principles so you can judge WHEN a technique applies, instead of copying trendy frameworks. Techniques churn; principles endure. + +## 4. Close the loop (calibrate your intuition) +- Before shipping, PREDICT the outcome and write it down. After shipping, compare to what actually happened. The gap is where product sense sharpens. + +## 5. Learn from strong product thinkers +- Work near them and critique with them if you can. If not, study Julie Zhuo, Stewart Butterfield, Rahul Vohra, Marty Cagan, Shreyas Doshi. + +Rule: product sense = heavy customer exposure + deliberate teardowns + closing the predict/outcome loop, repeated. A practice, not a vibe. diff --git a/src/plugins/product-manager/tools/get-product-sense.ts b/src/plugins/product-manager/tools/get-product-sense.ts new file mode 100644 index 0000000..f6bbc18 --- /dev/null +++ b/src/plugins/product-manager/tools/get-product-sense.ts @@ -0,0 +1,11 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { PRODUCT_SENSE_DOC } from "../data.js"; + +export function register(server: McpServer): void { + server.tool( + "product_manager_get_product_sense", + "How to develop product sense (Jules Walter, Julie Zhuo): customer immersion, product teardowns/critiques, principles-first learning, and closing the predict/outcome loop. The deliberate practice for building product judgment.", + {}, + async () => ({ content: [{ type: "text" as const, text: `# Developing product sense\n\n${PRODUCT_SENSE_DOC}\n` }] }), + ); +} diff --git a/tests/product-manager-behaviour.test.ts b/tests/product-manager-behaviour.test.ts index f5f1a32..d6e2a19 100644 --- a/tests/product-manager-behaviour.test.ts +++ b/tests/product-manager-behaviour.test.ts @@ -15,6 +15,7 @@ import { register as resolveDecision } from "../src/plugins/product-manager/tool import { register as getPrioritization } from "../src/plugins/product-manager/tools/get-prioritization.ts"; import { register as getDecisionTools } from "../src/plugins/product-manager/tools/get-decision-tools.ts"; import { register as getMvpScoping } from "../src/plugins/product-manager/tools/get-mvp-scoping.ts"; +import { register as getProductSense } from "../src/plugins/product-manager/tools/get-product-sense.ts"; // --- data layer --- test("four risks are exactly the canonical four", () => { @@ -133,3 +134,11 @@ test("get_mvp_scoping includes the riskiest assumption test", async () => { const text = extractTextContent(await tool.invoke({})); expect(text).toMatch(/Riskiest Assumption|riskiest assumption/); }); + +test("get_product_sense covers teardowns and customer immersion", async () => { + const tool = captureTool(getProductSense); + expect(tool.name).toBe("product_manager_get_product_sense"); + const text = extractTextContent(await tool.invoke({})); + expect(text).toMatch(/teardown/i); + expect(text).toMatch(/immersion|customer/i); +}); From 0edf9faf4911d9285ae0638574941ae53ab2f15c Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Mon, 22 Jun 2026 02:06:18 +0530 Subject: [PATCH 16/16] docs(persona): add marketing positioning research corpus (pass-1, cited source) --- ...26-06-21-marketing-positioning-corpus.json | 351 ++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 docs/research/2026-06-21-marketing-positioning-corpus.json diff --git a/docs/research/2026-06-21-marketing-positioning-corpus.json b/docs/research/2026-06-21-marketing-positioning-corpus.json new file mode 100644 index 0000000..2fb48eb --- /dev/null +++ b/docs/research/2026-06-21-marketing-positioning-corpus.json @@ -0,0 +1,351 @@ +{ + "summary": "Deep research harness — fan-out web searches, fetch sources, adversarially verify claims, synthesize a cited report.", + "agentCount": 109, + "logs": [ + "Q: How do the best product marketers and brand strategists do POSITIONING, MESSAGIN…", + "Decomposed into 6 angles: Positioning frameworks (primary source, step-by-step), Messaging & value proposition (StoryBrand SB7, VPC, strategic narrative), Category design & differentiation POV, Evidence-based brand growth laws (Sharp, Binet & Field), Brand archetypes, voice, segmentation & STP templates, Anti-patterns & behavioral positioning (mistakes, Rory Sutherland)", + "Positioning frameworks (primary source, step-by-step): 6 results", + "Category design & differentiation POV: 6 results", + "Evidence-based brand growth laws (Sharp, Binet & Field): 6 results", + "Evidence-based brand growth laws (Sharp, Binet & Field): 4 novel (2 filtered)", + "Messaging & value proposition (StoryBrand SB7, VPC, strategic narrative): 6 results", + "Messaging & value proposition (StoryBrand SB7, VPC, strategic narrative): 4 novel (2 filtered)", + "Anti-patterns & behavioral positioning (mistakes, Rory Sutherland): 6 results", + "Anti-patterns & behavioral positioning (mistakes, Rory Sutherland): 2 novel (4 filtered)", + "Brand archetypes, voice, segmentation & STP templates: 6 results", + "Brand archetypes, voice, segmentation & STP templates: 4 novel (2 filtered)", + "Fetched 26 sources → 122 claims → verifying top 25", + "\"Positioning is defined as making a product a leade…\": 3-0 ✓", + "\"April Dunford's positioning methodology decomposes…\": 3-0 ✓", + "\"The positioning process runs in a specific ordered…\": 3-0 ✓", + "\"April Dunford's positioning framework consists of …\": 3-0 ✓", + "\"Dunford's framework defines three distinct 'styles…\": 3-0 ✓", + "\"Raskin frames the company story as identical to co…\": 3-0 ✓", + "\"The Value Proposition Canvas has two sides: the Cu…\": 3-0 ✓", + "\"Customer Jobs are defined as the functional, socia…\": 3-0 ✓", + "\"Andy Raskin's strategic narrative method leads wit…\": 1-2 ✗", + "\"The book ships 11 named, fill-in-the-blank exercis…\": 3-0 ✓", + "\"Category Design's core thesis is 'different, not b…\": 3-0 ✓", + "\"'Fit' (problem-solution / product-market fit) is a…\": 0-3 ✗", + "\"The book provides a codifiable category-creation m…\": 3-0 ✓", + "\"Play Bigger's category design methodology follows …\": 1-2 ✗", + "\"The central thesis of category design is that a co…\": 3-0 ✓", + "\"The Category Design Scorecard codifies exactly fiv…\": 3-0 ✓", + "\"A defining trait of category designers is having a…\": 3-0 ✓", + "\"Tone of voice can be codified along four measurabl…\": 3-0 ✓", + "\"Maximum profit requires running BOTH brand buildin…\": 3-0 ✓", + "[v1:JTBD directs marketers to align product ] failed: You've hit your session limit · resets 1:10am (Asia/Kolkata)", + "[v2:JTBD directs marketers to align product ] failed: You've hit your session limit · resets 1:10am (Asia/Kolkata)", + "[v2:JTBD prescribes outcome-based segmentati] failed: You've hit your session limit · resets 1:10am (Asia/Kolkata)", + "[v1:JTBD prescribes outcome-based segmentati] failed: You've hit your session limit · resets 1:10am (Asia/Kolkata)", + "[v2:The recommended process for defining bra] failed: You've hit your session limit · resets 1:10am (Asia/Kolkata)", + "\"The recommended process for defining brand voice i…\": 2-0 (1 abstain) ✓", + "[v0:JTBD prescribes outcome-based segmentati] failed: You've hit your session limit · resets 1:10am (Asia/Kolkata)", + "\"JTBD prescribes outcome-based segmentation (groupi…\": 0-0 (3 abstain) ✗", + "\"The optimal marketing budget split is approximatel…\": 2-1 ✓", + "[v2:JTBD codifies customer needs as measurab] failed: You've hit your session limit · resets 1:10am (Asia/Kolkata)", + "[v0:JTBD directs marketers to align product ] failed: You've hit your session limit · resets 1:10am (Asia/Kolkata)", + "\"JTBD directs marketers to align product developmen…\": 0-0 (3 abstain) ✗", + "[v0:JTBD codifies customer needs as measurab] failed: You've hit your session limit · resets 1:10am (Asia/Kolkata)", + "[v0:Emotional advertising campaigns drive la] failed: You've hit your session limit · resets 1:10am (Asia/Kolkata)", + "[v2:Emotional advertising campaigns drive la] failed: You've hit your session limit · resets 1:10am (Asia/Kolkata)", + "\"Emotional advertising campaigns drive larger long-…\": 1-0 (2 abstain) ✗", + "[v1:JTBD codifies customer needs as measurab] failed: You've hit your session limit · resets 1:10am (Asia/Kolkata)", + "\"JTBD codifies customer needs as measurable 'desire…\": 0-0 (3 abstain) ✗", + "Verify done: 25 claims → 18 confirmed, 7 killed", + "[synthesize] failed: You've hit your session limit · resets 1:10am (Asia/Kolkata)" + ], + "result": { + "question": "How do the best product marketers and brand strategists do POSITIONING, MESSAGING, and BRAND STRATEGY? Cover concretely with codifiable frameworks: (1) Positioning - April Dunford's framework from Obviously Awesome (competitive alternatives, unique attributes, value/benefits, target market characteristics, market category, relevant trends) and her 10-step positioning process; Ries & Trout positioning principles; category design (Play Bigger / Category Pirates - the POV, the category narrative). (2) Messaging - Donald Miller StoryBrand SB7 framework (character, problem with 3 levels, guide, plan, call to action, failure, success); value proposition design (Osterwalder/Strategyn value proposition canvas - jobs/pains/gains, pain relievers, gain creators); messaging hierarchy and message pillars; the 'why now' narrative; Andy Raskin strategic narrative. (3) Segmentation & targeting - STP (segmentation-targeting-positioning), ICP definition, jobs-to-be-done applied to marketing. (4) Brand strategy - Mark & Pearson's 12 brand archetypes (the hero, the sage, the outlaw, etc. with traits/voice each); Byron Sharp 'How Brands Grow' evidence-based laws (mental availability, physical availability, distinctive brand assets, light buyers, double jeopardy law); Binet & Field 'The Long and the Short of It' (60/40 brand-vs-activation, emotional vs rational); Rory Sutherland behavioral/psychological positioning; brand voice and personality definition. Emphasize CODIFIABLE: step-by-step templates, fill-in-the-blank structures, named laws/principles, checklists, decision rules, and ANTI-PATTERNS (common positioning/branding mistakes). Prioritize primary sources (named authors' books, blogs, talks). The output will become the ground-truth corpus for a marketing persona/agent in an AI coding-agent system that must position and message ANY brand from scratch, so favor concrete reusable structures over generic advice.", + "summary": "Synthesis step was skipped or failed — returning 18 verified claims unmerged.", + "findings": [], + "confirmed": [ + { + "claim": "April Dunford's positioning framework consists of five named components: (1) Competitive Alternatives, (2) Key Unique Attributes, (3) Value (benefits the attributes enable), (4) Target Customer Segmentation, and (5) Market Category.", + "source": "https://www.aprildunford.com/post/a-quickstart-guide-to-positioning", + "quote": "1. **Competitive Alternatives** - What customers would do if your solution didn't exist\n2. **Key Unique Attributes** - Differentiated features or capabilities your product has that alternatives lack\n3. **Value** - The benefits those capabilities enable for buyers\n4. **Target Customer Segmentation** - Customers that care most about your differentiated value\n5. **Market Category** - The context positioning your product so value is obvious to targets", + "vote": "3-0" + }, + { + "claim": "The positioning process runs in a specific ordered sequence: start with competitive alternatives, then derive unique attributes, then translate attributes into value via 'So what for customers?', then define the target customers who care most about that value, then select the market category last.", + "source": "https://www.aprildunford.com/post/a-quickstart-guide-to-positioning", + "quote": "1. Start with **Competitive Alternatives**\n2. Identify **Key Unique Attributes** (what you have that alternatives lack)\n3. Determine **Value** (ask \"So what for customers?\")\n4. Define **Target Customers** (who cares most about this value?)\n5. Select **Market Category** (\"the context we position our product\")", + "vote": "3-0" + }, + { + "claim": "Positioning is defined as making a product a leader at delivering something a well-defined customer set cares about, and is explicitly distinct from messaging, taglines, and brand story/vision/why.", + "source": "https://www.aprildunford.com/post/a-quickstart-guide-to-positioning", + "quote": "\"Positioning defines how your product is a leader at delivering something that a well-defined set of customers cares a lot about.\"", + "vote": "3-0" + }, + { + "claim": "April Dunford's positioning methodology decomposes positioning into five component pieces, and a core part of the method is understanding how those pieces relate to each other.", + "source": "https://www.aprildunford.com/books", + "quote": "How to break positioning down into five component pieces ... How the component pieces relate to each other and why that matters", + "vote": "3-0" + }, + { + "claim": "Dunford's framework defines three distinct 'styles of positioning' as a decision tool for choosing the best market/category for a product.", + "source": "https://www.aprildunford.com/books", + "quote": "How to choose the best market for your product, including three styles of positioning", + "vote": "3-0" + }, + { + "claim": "Raskin frames the company story as identical to company strategy - the narrative is not decoration but the strategic backbone that aligns sales, marketing, fundraising, product, and recruiting.", + "source": "https://andyraskin.com/", + "quote": "The company story _is_ the company strategy. (Ben Horowitz, Andreessen Horowitz)", + "vote": "3-0" + }, + { + "claim": "The Value Proposition Canvas has two sides: the Customer Profile (Customer Jobs, Customer Pains, Customer Gains) and the Value Map (Products & Services, Pain Relievers, Gain Creators), where Pain Relievers map to pains and Gain Creators map to gains.", + "source": "https://www.strategyzer.com/library/value-proposition-design-book-summary", + "quote": "Pain Relievers — \"how your products and services alleviate specific customer pains\"; Gain Creators — \"how your offerings create positive outcomes customers desire\"", + "vote": "3-0" + }, + { + "claim": "Customer Jobs are defined as the functional, social, and emotional tasks customers try to accomplish, making 'jobs' a three-dimensional construct rather than purely functional.", + "source": "https://www.strategyzer.com/library/value-proposition-design-book-summary", + "quote": "Customer Jobs — \"functional, social, and emotional tasks customers try to accomplish\"", + "vote": "3-0" + }, + { + "claim": "Category Design's core thesis is 'different, not better': success comes from creating and dominating a new/different category rather than competing on incremental improvement. The book codifies this as a named exercise ('Be Different, Not Better') and a discipline that lets a company capture the majority of a category's value (citing Play Bigger research that one company earns 76% of total market-cap value created in tech categories).", + "source": "https://www.amazon.com/Laws-Category-Design-Somewhere-Different/dp/195693457X", + "quote": "Category Design is purpose-built for people who want to make an experiential difference, not an incremental better. Category Design is a business discipline that helps companies earn the majority of the value created in a specific new or different category of products or services. (Primary research on Category Design in Play Bigger... shows that in technology categories, one company earns 76% of the total market cap value created.)", + "vote": "3-0" + }, + { + "claim": "The book provides a codifiable category-creation methodology built on three named levers - a unique POV (point of view), Languaging, and a marketing strategy called a Lightning Strike - which together make a company the 'Category King or Queen' and make it impossible for competitors to copy.", + "source": "https://www.amazon.com/Laws-Category-Design-Somewhere-Different/dp/195693457X", + "quote": "22 Category Design principles that, when combined together, can help you become a Category King or Queen and make it impossible for someone else to \"do what you do\" How to create new categories and redefine existing ones using a unique POV, Languaging, and a powerful marketing strategy called a Lightning Strike", + "vote": "3-0" + }, + { + "claim": "The book ships 11 named, fill-in-the-blank exercises (a reusable template set) including 'Start Rejecting the Premise', 'Put Your Category First, and Your Brand Second', 'How to Frame, Name, and Claim a Category', 'Be Different, Not Better', and 'How to Find Your Superconsumers' - directly codifying the positioning/category-design workflow.", + "source": "https://www.amazon.com/Laws-Category-Design-Somewhere-Different/dp/195693457X", + "quote": "You will start applying these principles through 11 in-book exercises: Start Rejecting the Premise How to Find Your Superconsumers Put Your Category First, and Your Brand Second How to Frame, Name, and Claim a Category Be Different, Not Better Create Net-New Demand For Your Category and Company Build Your DAM the Demand Strategy Create Your Digital Air Offerings Figure Out Your Pricing Innovate Your Business Model Find Your WOM Super-Geos", + "vote": "3-0" + }, + { + "claim": "The central thesis of category design is that a company should design and lead a new category rather than compete in an existing one, because the market does not yet perceive the company's differentiation.", + "source": "https://www.playbigger.com/", + "quote": "DESIGN THE CATEGORY, CREATE THE CONDITIONS TO WIN... design the category they can lead - and build the belief to make it real.", + "vote": "3-0" + }, + { + "claim": "The Category Design Scorecard codifies exactly five named indicators that distinguish category designers/creators from companies merely competing in existing categories: (1) Category POV, (2) Future Category Reimagined and Without Compromise, (3) Radically Different Offer + Business Model, (4) The Data Flywheel, (5) Depth & Degree of Customer Outcomes.", + "source": "https://lochhead.com/category-design-scorecard/", + "quote": "The tool identifies \"five key indicators\" that differentiate category designers from other successful companies.", + "vote": "3-0" + }, + { + "claim": "A defining trait of category designers is having a clear Category Point of View framed around a large problem treated as a noble cause/purpose, not incremental product positioning.", + "source": "https://lochhead.com/category-design-scorecard/", + "quote": "\"Category designers...are going after something that they see as a noble cause, a noble purpose, or a giant problem worth solving.\"", + "vote": "3-0" + }, + { + "claim": "The optimal marketing budget split is approximately 60% brand building and 40% sales activation, identified by maximizing the number of business effects across the IPA case databank.", + "source": "https://www.newisnew.lt/wp-content/uploads/2016/07/1.-Les-Binet.pdf", + "quote": "Budgets must be balanced ... Optimum mix: ~60% brand, ~40% activation", + "vote": "2-1" + }, + { + "claim": "Maximum profit requires running BOTH brand building and sales activation together; the 'Both' campaign objective yields a higher profit effect than either brand building alone or activation alone.", + "source": "https://www.newisnew.lt/wp-content/uploads/2016/07/1.-Les-Binet.pdf", + "quote": "For maximum profit, you need both ... Profit effect ... Brand buiding | Both | Sales activation", + "vote": "3-0" + }, + { + "claim": "Tone of voice can be codified along four measurable dimensions, each defined by two opposing poles: Formal vs. Casual, Serious vs. Funny, Respectful vs. Irreverent, and Matter-of-fact vs. Enthusiastic. This gives a brand a reusable scoring framework for defining and auditing its voice.", + "source": "https://www.nngroup.com/articles/tone-of-voice-dimensions/", + "quote": "Formal vs. Casual: 'Is the writing formal? Informal? Casual?' Serious vs. Funny: 'Is the writer trying to be humorous? Or is the subject approached in a serious way?' Respectful vs. Irreverent: 'Does the writer approach the subject in a respectful way? Or does she take an irreverent approach?' Matter-of-fact vs. Enthusiastic: 'Does the writer seem to be enthusiastic about the subject?... Or is the writing dry and matter-of-fact?'", + "vote": "3-0" + }, + { + "claim": "The recommended process for defining brand voice is to first pick where the brand sits on the four dimensions, then refine with more specific target tone words (e.g. 'playful', 'quirky', 'sarcastic'), and to explicitly choose 'anti-tone' words naming the tones the brand wants to avoid.", + "source": "https://www.nngroup.com/articles/tone-of-voice-dimensions/", + "quote": "Decide what combination of dimensions makes sense best for your company... Refine your tone strategy by choosing more specific tone target words like 'playful,' 'quirky,' or 'sarcastic.'... Consider choosing anti-tone words. These are the tones that you want to avoid creating.", + "vote": "2-0" + } + ], + "refuted": [ + { + "claim": "Andy Raskin's strategic narrative method leads with the customer's future end-state ('the Promised Land') rather than with the product, making the desired customer experience the starting point of the story.", + "vote": "1-2", + "source": "https://andyraskin.com/" + }, + { + "claim": "'Fit' (problem-solution / product-market fit) is achieved when the value proposition addresses the customer's most important pains and gains, with a codifiable target of roughly 50-70% of the most significant pains and gains.", + "vote": "0-3", + "source": "https://www.strategyzer.com/library/value-proposition-design-book-summary" + }, + { + "claim": "Play Bigger's category design methodology follows a named four-stage blueprint: Define the problem, Frame the category, Mobilize the company, Own the market.", + "vote": "1-2", + "source": "https://www.playbigger.com/" + }, + { + "claim": "Emotional advertising campaigns drive larger long-term profit growth than rational campaigns, with the profit gap widening over a 1, 2, and 3+ year horizon; rational and emotional campaigns work differently (rational stronger on short-term activation, emotional stronger on brand effects).", + "vote": "1-0", + "source": "https://www.newisnew.lt/wp-content/uploads/2016/07/1.-Les-Binet.pdf" + }, + { + "claim": "JTBD codifies customer needs as measurable 'desired outcomes' - the metrics customers use to judge success at getting a job done - not vague preferences, which gives marketers concrete, checkable need statements to message against.", + "vote": "0-0", + "source": "https://strategyn.com/jobs-to-be-done/" + }, + { + "claim": "JTBD prescribes outcome-based segmentation (grouping customers by unique sets of unmet needs) as a replacement for demographic, psychographic, or behavioral segmentation, which it claims cannot surface these segments.", + "vote": "0-0", + "source": "https://strategyn.com/jobs-to-be-done/" + }, + { + "claim": "JTBD directs marketers to align product development, messaging, and positioning to the unmet desired outcomes of a target segment, and to align the whole organization around one consistent, problem-focused messaging strategy.", + "vote": "0-0", + "source": "https://strategyn.com/jobs-to-be-done/" + } + ], + "sources": [ + { + "url": "https://www.aprildunford.com/post/a-quickstart-guide-to-positioning", + "quality": "primary", + "claimCount": 5 + }, + { + "url": "https://www.heinzmarketing.com/blog/10-step-positioning-process-an-obviously-awesome-book-summary-part-3/", + "quality": "secondary", + "claimCount": 5 + }, + { + "url": "https://www.kathirvel.com/guide-april-dunford-positioning-framework/", + "quality": "secondary", + "claimCount": 5 + }, + { + "url": "https://www.aprildunford.com/books", + "quality": "primary", + "claimCount": 5 + }, + { + "url": "https://readingraphics.com/book-summary-obviously-awesome/", + "quality": "secondary", + "claimCount": 4 + }, + { + "url": "https://userlist.com/blog/positioning-overhaul/", + "quality": "blog", + "claimCount": 5 + }, + { + "url": "https://medium.com/the-mission/the-greatest-sales-deck-ive-ever-seen-4f4ef3391ba0", + "quality": "blog", + "claimCount": 5 + }, + { + "url": "https://andyraskin.com/", + "quality": "primary", + "claimCount": 5 + }, + { + "url": "https://www.strategyzer.com/library/value-proposition-design-book-summary", + "quality": "primary", + "claimCount": 5 + }, + { + "url": "https://www.creativeo.co/post/storybrand-framework", + "quality": "blog", + "claimCount": 5 + }, + { + "url": "https://www.categorypirates.com/post/what-is-category-design", + "quality": "blog", + "claimCount": 5 + }, + { + "url": "https://www.amazon.com/Laws-Category-Design-Somewhere-Different/dp/195693457X", + "quality": "primary", + "claimCount": 5 + }, + { + "url": "https://www.amazon.com/Category-Design-Toolkit-Frameworks-Dominating/dp/195693412X", + "quality": "secondary", + "claimCount": 5 + }, + { + "url": "https://medium.com/firm-narrative/to-create-a-new-category-name-the-new-game-70c2e55edc2e", + "quality": "blog", + "claimCount": 3 + }, + { + "url": "https://www.playbigger.com/", + "quality": "primary", + "claimCount": 4 + }, + { + "url": "https://lochhead.com/category-design-scorecard/", + "quality": "primary", + "claimCount": 5 + }, + { + "url": "https://marketingscience.info/news-and-insights/summary-on-byron-sharps-laws-of-brand-growth-with-examples", + "quality": "secondary", + "claimCount": 3 + }, + { + "url": "https://www.alexmurrell.co.uk/summaries/byron-sharp-how-brands-grow", + "quality": "blog", + "claimCount": 5 + }, + { + "url": "https://www.alexmurrell.co.uk/summaries/les-binet-and-peter-field-the-long-and-the-short-of-it", + "quality": "blog", + "claimCount": 5 + }, + { + "url": "https://www.newisnew.lt/wp-content/uploads/2016/07/1.-Les-Binet.pdf", + "quality": "primary", + "claimCount": 5 + }, + { + "url": "https://iconicfox.com.au/brand-archetypes/", + "quality": "blog", + "claimCount": 5 + }, + { + "url": "https://www.nngroup.com/articles/tone-of-voice-dimensions/", + "quality": "primary", + "claimCount": 3 + }, + { + "url": "https://strategyn.com/jobs-to-be-done/", + "quality": "primary", + "claimCount": 5 + }, + { + "url": "https://fullfunnel.io/ideal-customer-profile/", + "quality": "blog", + "claimCount": 5 + }, + { + "url": "https://www.42courses.com/blog/home/rory-sutherlands-11-rules-of-alchemy", + "quality": "secondary", + "claimCount": 5 + }, + { + "url": "https://strategicmarketingtribe.com/marketing-news/b/positioning-ries-trout-summary-small-business", + "quality": "blog", + "claimCount": 5 + } + ], + "stats": { + "angles": 6, + "sources": 26, + "claims": 122, + "verified": 25, + "confirmed": 18, + "killed": 7, + "afterSynthesis": 0 + } + } +} \ No newline at end of file