cpq-builder

Configure-Price-Quote (CPQ) vertical skill for the Customware SPA. Defines the section layout, config schema, business rule templates, and deterministic mapping rules for transforming a DOMAIN.md into a CPQ application. Use this skill when the Builder Agent classifies a customer's domain as any system that collects structured inputs, applies calculation rules, produces an output document, and optionally routes it for review. Trigger signals: quoting, pricing, product configuration, calculators, guided intake forms, assessment tools, estimate builders, proposal workflows, eligibility checkers, any "fill in fields → calculate → produce a document" pattern.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "cpq-builder" with this command: npx skills add customware-ai/skills/customware-ai-skills-cpq-builder

CPQ Builder Skill

What This Skill Does

This skill defines how to build a Configure-Price-Quote tool — any system where users configure inputs, the system applies rules to calculate results, and the output is a formatted document that may require review/approval.

CPQ is NOT limited to product pricing. The same structural pattern covers:

Domain"Configure""Price" (Calculate)"Quote" (Output)
Equipment salesSelect product, pick optionsApply markup, calculate totalsSales quote PDF
Legal calculatorsEnter case details (income, duration)Apply guideline formulasSummary report with estimates
Insurance estimatorsEnter coverage detailsApply rate tablesPremium estimate document
Loan qualificationEnter financial detailsApply lending criteriaQualification letter
Benefits eligibilityEnter personal detailsApply eligibility rulesBenefits summary
Service proposalsSelect services, set scopeApply labor ratesService proposal

The builder reads this skill, reads the DOMAIN.md for the specific domain terminology and rules, and generates a working prototype. The DOMAIN.md determines whether the tool is about crane quotes or divorce calculations — the skill defines the structure.

When to Use This Skill

The Builder Agent should select this skill when the customer's DOMAIN.md contains:

Classic CPQ signals:

  • Products or services that are quoted/priced for customers
  • Configuration options (sizes, models, variants, materials)
  • Dependencies between products (requires, recommends, excludes)
  • Markup or margin-based pricing (cost-plus, vendor list + percentage)
  • A quoting or proposal workflow (draft → review → approve → send)

Broader "configure-calculate-output" signals:

  • A calculator, estimator, or assessment tool
  • Guided intake forms where inputs drive calculated outputs
  • Multi-step data collection with rules applied to produce results
  • Output documents (reports, summaries, estimates, proposals)
  • A preparer/reviewer workflow (client fills in → professional reviews)
  • "Fill out this form based on these guidelines and get an estimate"

Classification signals from the Clarence transcript:

  • "quoting is a mess," "inconsistent pricing," "reps don't know what goes with what"
  • "calculator," "estimator," "guided form," "step-through guide"
  • "fill out fields and get a result," "apply the guidelines," "produce a report"
  • "client submits, then the [professional] reviews"
  • Products with accessories, parts, or services that must go together

Do NOT use this skill when the domain is primarily about inventory tracking (use ERP skill), ongoing project execution with field tracking and payments (use trades-builder), online product sales (use e-commerce skill), or customer relationship management (use CRM skill).


Template Contract

Before you start building, understand what the template gives you and what this skill adds. This is the contract:

The template (app/layouts/MainLayout.tsx) ships with:

  • SidebarProvider, Sidebar, SidebarContent, SidebarInset, SidebarTrigger — already wired
  • SidebarContent is empty — this is your landing zone
  • One brand slot in the header (logo placeholder + company name)
  • ModeToggle and user menu in the header's right cluster

This skill fills:

  • SidebarContent — with the vertical stepper and saved items (see Layout Pattern below)
  • The brand slot in the header — with the client's logo and company name from DOMAIN.md
  • The header's right cluster — adds a role switcher DropdownMenu before the existing user menu
  • The <Outlet /> in <main> — via route components for each of the four sections

This skill does NOT:

  • Add a second Sidebar component. There is one sidebar.
  • Put a brand tile inside SidebarContent. Brand lives in the header only.
  • Rewire SidebarProvider or replace the collapsible behavior. Use what's there.
  • Put the stepper as horizontal tabs in the main content area. The stepper is a vertical list inside SidebarContent.

If you find yourself wanting to restructure MainLayout.tsx, stop — the answer is almost always to fill SidebarContent instead.


Section Definitions

The CPQ application has three navigable sections plus a fourth output view. The Builder Agent uses these as-is.

Section count rationale: CPQ has FOUR sections, not five. The older "Preview" section has been removed — its job (showing subtotal, tax, total before approval) is already done by the always-on Live Summary panel in the right sidebar, which updates continuously as the user configures the quote. A dedicated Preview step would be redundant. If a review gate before approval is genuinely needed, it belongs as a confirmation dialog on the Approve action, not as its own stepper step.

{
  "sections": [
    {
      "id": "configure",
      "label": "Configure",
      "icon": "Settings2",
      "order": 1,
      "component": "card-selector",
      "componentConfig": {
        "itemLayout": "grid",
        "showPrice": true,
        "showDescription": true,
        "showOptions": true,
        "selectionMode": "add-to-list",
        "groupBy": "category",
        "capturePricing": true
      },
      "dataSource": "data.products",
      "actions": [
        { "label": "Add to Quote", "action": "addSelected", "variant": "primary" }
      ]
    },
    {
      "id": "quote",
      "label": "Build Quote",
      "icon": "FileText",
      "order": 2,
      "component": "data-table",
      "componentConfig": {
        "columns": [
          { "key": "product", "label": "Product", "width": "auto" },
          { "key": "options", "label": "Configuration", "width": "auto" },
          { "key": "quantity", "label": "Qty", "width": "80px", "editable": true },
          { "key": "unitPrice", "label": "Unit Price", "width": "120px", "format": "currency", "editable": true },
          { "key": "total", "label": "Total", "width": "120px", "format": "currency", "computed": true }
        ],
        "showRowActions": true,
        "rowActions": ["edit", "duplicate", "remove"],
        "showTotalsFooter": true
      },
      "dataSource": "data.lineItems",
      "actions": [
        { "label": "Continue to Approval", "action": "navigateTo:approve", "variant": "primary" },
        { "label": "Clear All", "action": "clearItems", "variant": "ghost", "confirm": true }
      ]
    },
    {
      "id": "approve",
      "label": "Approve",
      "icon": "CheckCircle",
      "order": 3,
      "component": "form",
      "componentConfig": {
        "fields": [
          { "key": "customerName", "label": "Customer Name", "type": "text", "required": true },
          { "key": "customerEmail", "label": "Customer Email", "type": "email" },
          { "key": "paymentTerms", "label": "Payment Terms", "type": "select", "default": "net30" },
          { "key": "notes", "label": "Notes", "type": "textarea" },
          { "key": "validUntil", "label": "Valid Until", "type": "date" }
        ],
        "confirmBeforeSubmit": true
      },
      "dataSource": "data.quoteSettings",
      "gated": {
        "requires": ["customerName", "hasLineItems", "hasPricing", "noErrors"],
        "message": "Complete these before approving: customer name, at least one product with unit price, no unresolved dependency errors."
      },
      "actions": [
        { "label": "Approve & Send", "action": "approveQuote", "variant": "primary", "confirm": true },
        { "label": "Save Draft", "action": "saveDraft", "variant": "secondary" }
      ]
    },
    {
      "id": "document",
      "label": "Quote Document",
      "icon": "FileOutput",
      "order": 4,
      "component": "quote-document",
      "componentConfig": {
        "readOnly": true,
        "printable": true,
        "showBrandHeader": true,
        "showLineItemsTable": true,
        "showTotalsBlock": true,
        "showTermsBlock": true
      },
      "dataSource": "data.currentQuote",
      "actions": [
        { "label": "Back to Approve", "action": "navigateTo:approve", "variant": "ghost" }
      ]
    }
  ],
  "navMode": "stepper"
}

Navigation mode is stepper — the sidebar shows all sections as sequential steps. All steps are clickable at any time (not a wizard). The Approve step shows gating indicators when requirements aren't met. The Quote Document step becomes clickable once a quote is approved or saved as draft.

Pricing Capture is Mandatory (Not Optional)

A CPQ tool without pricing is not CPQ — it is intake routing. The Builder Agent MUST capture pricing during the Configure or Build Quote step when the domain has any pricing information. This is a hard requirement, not a preference.

What triggers pricing capture:

  • DOMAIN.md Entity Registry notes mention prices, rates, or costs for any entity ("Example price: $15,000")
  • DOMAIN.md has an Approved Pricing Reference, rate card, or pricing table
  • DOMAIN.md Business Rules reference monetary thresholds or calculations ("discounts over 15% require approval")
  • The domain is clearly a pricing/quoting domain (customer mentioned currency, tax, payment terms, quotes, estimates, proposals)

What the build must include when pricing is triggered:

  1. Unit price field in the Configure or Build Quote step — either as a number input (simple case) or as a product picker that loads the price from a pricing table (catalog case). Never as a hardcoded constant.
  2. Quantity field in the Build Quote step line items — default to 1, editable.
  3. Line total computation — unit price × quantity, displayed per line and as a footer subtotal.
  4. Tax line — computed as a percentage of subtotal. The tax name (HST, GST, VAT, Sales Tax) and rate come from DOMAIN.md. If the rate isn't specified, default to the jurisdiction's standard (HST → 13% for Ontario, GST → 5% for Canada-wide, etc.) and note the assumption in the completion summary.
  5. Grand total — subtotal + tax, displayed prominently.
  6. Payment terms — from DOMAIN.md (Net 30, Net 45, etc.) captured in the Approve step.
  7. Currency — from DOMAIN.md. Format all money displays with the currency symbol or code (e.g., "CA$15,000.00" or "$15,000.00 CAD").

When pricing info is partial or missing:

If the domain is clearly a pricing domain but DOMAIN.md doesn't have specific price values, do NOT skip pricing capture — still build the pricing UI with empty/editable fields so the user can fill them in. Then note the gap in the completion summary: "DOMAIN.md mentions pricing but does not specify unit prices. Pricing UI is present and editable; unit prices are empty and must be entered by the user."

Example values from DOMAIN.md are seed data, not constants:

If DOMAIN.md says "Example price: $15,000," that value should appear as a default/seed in localStorage pricing data — NOT hardcoded in the UI, NOT embedded in rule logic. The user can change it. This is consistent with the "examples are data points, not canonical rules" extraction rule in the Knowledge Agent.

Failure mode to avoid: A CPQ prototype that shows "Quote Document" with Customer, Product, Motor, Status, and Terms — but no price, no subtotal, no tax, no grand total. That is a routing workflow masquerading as a quote. If the domain has any pricing signal, that build is incomplete regardless of how clean the UI looks.


Layout Pattern

The CPQ application uses a three-panel layout with FOUR sections (not five). The builder MUST follow this layout — not a single scrolling page with all sections stacked.

The four sections are: Configure, Build Quote, Approve, Quote Document. The task description from the project agent may only list three stages — the Quote Document is always added as the fourth section by this skill regardless of what the task says. Do not skip it.

The older Preview step has been removed. Its role (showing subtotal + tax + total before approval) is fully covered by the always-visible Live Summary panel in the right sidebar, which updates as the user configures the quote. Do not re-introduce a Preview step.

Left sidebar (always visible, collapsible)

The template ships SidebarProvider, Sidebar, SidebarContent, and SidebarTrigger already wired in app/layouts/MainLayout.tsx. SidebarContent is empty — that's the slot this skill fills. Do not re-wire the sidebar, do not add a second Sidebar component, and do not put a brand tile inside it. Brand identity lives in the header only (see Template Contract above).

Sidebar heading: Use a contextual label like "Quote workflow" or "Business process" — not the company name. The heading describes what the navigation IS, not who it's for.

ComponentContent
StepperA VERTICAL list of ALL FOUR CPQ sections inside SidebarContent: (1) Configure, (2) Build Quote, (3) Approve, (4) Quote Document. Each step shows: step number, label, subtitle, and completion state (pending / active / done with checkmark). Clicking a step navigates to that section's panel. This is a vertical stepper in the sidebar — NOT horizontal tabs in the main content area.
Saved itemsList of saved records stored in localStorage. Each shows name + status badge (Draft / Awaiting Review / Approved). Clicking loads the record. "New" button at top. Pin this section to the bottom of the sidebar so it's always visible without scrolling — use flexbox with stepper taking available space and saved items fixed at the bottom. Double-click a name to rename inline. The label should match the domain: "Saved quotes" for product domains, "Saved submissions" for intake/calculator domains, or whatever DOMAIN.md calls them.

The role switcher is in the header bar as a dropdown — not in the left sidebar. See Layout Principles in the builder prompt.

Main content (center — changes per section)

Only the active section renders. Do NOT stack all four sections on one scrolling page. Do NOT render sections as side-by-side cards. Each stepper step shows its corresponding panel at FULL WIDTH of the main content area. All other panels are hidden.

Do NOT put the stepper as horizontal tabs at the top of the main content area. The stepper belongs in the sidebar as a vertical list. The template's SidebarContent is already empty and waiting — fill it there.

Implementation: use a currentStep state variable (0–3, not 0–4). Render only the panel that matches currentStep. When the user clicks a stepper step, update currentStep and only that panel appears.

// Correct: ALL FOUR panels, conditional rendering
{currentStep === 0 && <ConfigurePanel />}
{currentStep === 1 && <BuildQuotePanel />}
{currentStep === 2 && <ApprovePanel />}
{currentStep === 3 && <QuoteDocumentPanel />}

If you define a WorkflowStep type, it must include all four:

type WorkflowStep = "configure" | "build" | "approve" | "document";
// WRONG: all panels visible, scroll to find the right one
<ConfigurePanel />
<BuildQuotePanel />
<ApprovePanel />
<QuoteDocumentPanel />
SectionWhat renders
ConfigureInput selection or data entry. For product domains: product cards with options AND unit price fields — pricing capture is mandatory when the domain has pricing signals (see Pricing Capture is Mandatory above). For calculator/intake domains: guided form sections with input fields, dropdowns, and validation. The DOMAIN.md determines which pattern — if entities have prices and options, use product cards with editable price fields. If entities are form fields with rules, use guided form sections.
Build QuoteResults assembly and review. For product domains: line items table with editable quantities, editable unit prices, and computed line totals, plus a totals footer showing subtotal + tax + grand total. For calculator/intake domains: calculated results table showing inputs → applied rules → outputs. Editable where the domain allows adjustments.
ApproveApproval owner display, status badge (Pending / Approved / Rejected). Approve and reject buttons, gated by presence of pricing data (cannot approve a priceless quote when the domain is a pricing domain). Gated by role — only roles with approval permission can approve. Other roles see a message: "You are viewing as [Name]. Only [approver names] can approve." For intake domains: the reviewer (lawyer, underwriter, advisor) reviews the submission. Clicking Approve should show a confirmation dialog summarizing the final totals before committing, since the old Preview step has been removed.
Quote DocumentThe final formatted output — what you would send to the customer or print. For product domains: a proper sales quote with brand header, line items table, totals, and terms. For calculator/intake domains: a summary report with inputs, calculations, results, and any disclaimers. See "Quote Document" section below for the required layout.

Quote Document (final output view)

This is the polished, recipient-facing document. It renders as a clean, printable layout inside the main content area — styled as if it could be handed directly to the customer. This is the actual deliverable the CPQ tool produces. Treat it as such: it is not a summary card, it is a document.

Minimum quality bar: if you would be embarrassed to email this to North Shore Fabrication as "the quote," it is not done. If it omits price, line items, tax, or grand total on a pricing domain, it is not done.

Layout — top to bottom, each a distinct block:

1. Document header block — two columns:

  • Left: BrandMark component with size="lg", the full company name, and company address (placeholder address if not in DOMAIN.md: "123 Main St, City, Province, Postal Code — update in settings").
  • Right: document title ("Quote"), document reference number (auto-generated, e.g., "Q-2026-0001"), document date, "Valid until" date (from Approve step).

Use BrandMark, not a bare <img>. The logo falls back to a tinted initials square gracefully on failure.

2. Prepared-for block — a labeled "Prepared for" section with the customer's name, contact email, and any customer company name captured during Configure. Give this block visual weight — it's the most important identity on the document.

3. Line items table — for product/pricing domains, a proper table with these columns:

ColumnContent
#Line number (1, 2, 3...)
DescriptionProduct name + configuration details ("Single girder crane — Motor A")
QtyQuantity
Unit PricePer-unit price, formatted with currency
Line TotalQty × Unit Price, formatted with currency

For calculator/intake domains: an inputs table (left column: input name, right column: value) instead of a line items table, followed by a results table (left: result name, right: computed value with formula note).

4. Totals block — right-aligned, structured as a stack:

Subtotal:                    $XX,XXX.00
HST (13%):                    $X,XXX.00
─────────────────────────────────────────
Total:                       $XX,XXX.00 CAD

The Total row should be visually prominent — larger font, bold, full currency label (e.g., "CAD", "USD").

5. Terms block — a clearly delimited section with:

  • Payment Terms (e.g., "Net 45")
  • Currency (e.g., "Canadian Dollars (CAD)")
  • Validity period (e.g., "This quote is valid for 30 days")
  • Any mandatory terms or disclaimers from DOMAIN.md (e.g., for legal calculators, the required "this is an estimate, not legal advice" language)

6. Status and approval block — status badge (Draft / Awaiting Review / Approved), approved-by name if applicable, approval date if applicable.

7. Footer — small print area for additional terms, contact info, or signature lines if the domain requires them.

This view is read-only. No edit controls anywhere on the document. The only action available is "Back to Approve" (which returns to the stepper). If the user wants to change something, they click back to Configure or Build Quote in the stepper. The Quote Document is the output, not a workspace.

Reference implementation (abbreviated):

<article className="bg-card border rounded-lg p-8 max-w-4xl mx-auto print:border-0 print:shadow-none">
  {/* 1. Header */}
  <header className="flex justify-between items-start pb-6 border-b">
    <div className="flex items-center gap-4">
      <BrandMark logoUrl={BRAND_LOGO_URL} companyName={COMPANY_NAME} size="lg" />
      <div>
        <h1 className="text-xl font-bold">{COMPANY_NAME}</h1>
        <p className="text-sm text-muted-foreground">{COMPANY_ADDRESS}</p>
      </div>
    </div>
    <div className="text-right">
      <h2 className="text-2xl font-bold">Quote</h2>
      <p className="text-sm">{quote.ref}</p>
      <p className="text-sm text-muted-foreground">Date: {quote.date}</p>
      <p className="text-sm text-muted-foreground">Valid until: {quote.validUntil}</p>
    </div>
  </header>

  {/* 2. Prepared for */}
  <section className="py-6">
    <h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide mb-2">
      Prepared for
    </h3>
    <p className="text-lg font-medium">{quote.customerName}</p>
    {quote.customerEmail && <p className="text-sm">{quote.customerEmail}</p>}
  </section>

  {/* 3. Line items */}
  <Table>
    <TableHeader>
      <TableRow>
        <TableHead className="w-12">#</TableHead>
        <TableHead>Description</TableHead>
        <TableHead className="w-20 text-right">Qty</TableHead>
        <TableHead className="w-32 text-right">Unit Price</TableHead>
        <TableHead className="w-32 text-right">Total</TableHead>
      </TableRow>
    </TableHeader>
    <TableBody>
      {quote.lineItems.map((item, i) => (
        <TableRow key={item.id}>
          <TableCell>{i + 1}</TableCell>
          <TableCell>
            <div className="font-medium">{item.product}</div>
            <div className="text-sm text-muted-foreground">{item.configuration}</div>
          </TableCell>
          <TableCell className="text-right">{item.quantity}</TableCell>
          <TableCell className="text-right">{formatCurrency(item.unitPrice, quote.currency)}</TableCell>
          <TableCell className="text-right">{formatCurrency(item.lineTotal, quote.currency)}</TableCell>
        </TableRow>
      ))}
    </TableBody>
  </Table>

  {/* 4. Totals */}
  <section className="flex justify-end py-6">
    <dl className="w-80 space-y-2">
      <div className="flex justify-between text-sm">
        <dt>Subtotal:</dt>
        <dd>{formatCurrency(quote.subtotal, quote.currency)}</dd>
      </div>
      <div className="flex justify-between text-sm">
        <dt>{quote.taxName} ({quote.taxRate}%):</dt>
        <dd>{formatCurrency(quote.taxAmount, quote.currency)}</dd>
      </div>
      <div className="flex justify-between text-lg font-bold pt-2 border-t">
        <dt>Total:</dt>
        <dd>{formatCurrency(quote.total, quote.currency)} {quote.currency}</dd>
      </div>
    </dl>
  </section>

  {/* 5. Terms */}
  <section className="py-6 border-t">
    <h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide mb-2">
      Terms
    </h3>
    <dl className="grid grid-cols-2 gap-y-2 text-sm">
      <dt className="font-medium">Payment Terms:</dt>
      <dd>{quote.paymentTerms}</dd>
      <dt className="font-medium">Currency:</dt>
      <dd>{quote.currencyName}</dd>
      <dt className="font-medium">Validity:</dt>
      <dd>Valid until {quote.validUntil}</dd>
    </dl>
  </section>

  {/* 6. Status */}
  <footer className="pt-6 border-t flex justify-between items-center">
    <StatusBadge status={quote.status} />
    {quote.approvedBy && (
      <p className="text-sm text-muted-foreground">
        Approved by {quote.approvedBy} on {quote.approvedDate}
      </p>
    )}
  </footer>
</article>

What the document must NOT look like:

A 5-line key-value list showing "Customer: X, Product: Y, Motor: Z, Status: Awaiting Review, Terms: Net 45" inside a single card is not a Quote Document. It's a summary blurb. A customer receiving that would have no idea what they're being quoted, for how much, or what they're agreeing to. If your Quote Document would fit inside the Live Summary panel, you have not built a Quote Document.

Right sidebar (always visible)

ComponentContent
Live summaryLive-updating card: For product domains — selected product, chosen options, running subtotal, tax, total. For calculator domains — key inputs entered so far, preliminary results. Updates immediately when the user changes any input.
Workflow notesBusiness rules and routing info from DOMAIN.md displayed as contextual guidance cards. For calculator domains, this can include "Guidelines applied" or "Rules being used."

RBAC behavior

  • Seed localStorage with roles from DOMAIN.md User Roles or Stakeholder Map.
  • Role switcher is a single DropdownMenu dropdown in the header bar — not separate buttons for each role. The dropdown trigger shows the active role name and badge (e.g., "Jeff — Approver"). Clicking opens the menu listing all roles. Do NOT render each role as a separate button in the header row. One trigger, one dropdown menu. Selecting a new role updates the view immediately.
  • Approval gating: If DOMAIN.md says "only Andy or Jeff can approve," disable the Approve button when viewing as Dre or Manish. Show a clear message explaining why.
  • Routing visibility: If a role handles specific work types (e.g., "Dre handles maintenance"), show relevant routing info when that role is active.

Price and results visibility

  • For pricing domains: Show prices on EVERY screen where products or options appear. Option selection immediately updates the right sidebar Live Summary total. The Build Quote section shows subtotal + tax = total in a footer row. Use the tax type from DOMAIN.md (HST = 13%, GST = 5%). Currency from DOMAIN.md (CAD, USD) appears in all price displays. The Quote Document shows the full totals block as the recipient would see it.
  • For calculator/intake domains: Show calculated results as they become available. If partial results can be computed from the inputs entered so far, show them in the right sidebar Live Summary. The Build Quote section shows the full calculation results. Use the output format specified in DOMAIN.md (monetary amounts, percentages, ranges, scores). The Quote Document renders as a formal summary report.

shadcn/ui component mapping

Use these shadcn/ui components for each CPQ element. Do not build custom equivalents when an existing component does the job. Import from ~/components/ui/*.

CPQ ElementComponentSourceUsage Notes
Product cardCard, CardHeader, CardContentshadcnShow product name, description, base price.
Motor / option selectionRadioGroup, RadioGroupItemshadcnMutually exclusive options. Show price next to each label. Wrap in a Card.
Optional items (installation)Checkbox with labelshadcnToggle on/off. Show description below the label.
Status badgesBadgeshadcnUse variant="outline" for Draft, variant="default" for Awaiting Approval, variant="secondary" for Approved. Apply semantic colors via className.
Role badgesBadgeshadcnvariant="outline" for Staff, variant="default" for Approver, variant="secondary" for View only.
Line items tableTable, TableHeader, TableRow, TableCellshadcnClean header row, right-align price columns. Use TableFooter for subtotal row.
Saved quotes listCustom list with BadgeshadcnEach item is a clickable row with Badge for status. Use ScrollArea if the list could overflow.
Form inputsInput, LabelshadcnQuote name, customer name, notes. Stack label above input.
DropdownsSelect, SelectContent, SelectItemshadcnLead source, assigned-to, motor selection (alternative to RadioGroup for compact layouts).
Action buttonsButtonshadcnPrimary: variant="default" with brand accent. Secondary: variant="outline". Destructive: variant="destructive".
Section dividersSeparatorshadcnBetween sections within a panel. Horizontal, subtle.
Stepper navigationCustom (no library component)Build as a vertical list of clickable items inside SidebarContent. Use cn() for active/done/pending states. Step number in a circle, label, subtitle, completion checkmark.
Role switcherDropdownMenushadcnIn the header. Shows active role name and badge. Dropdown lists all roles.
Quote summary sidebarCard with stacked label/value rowsshadcnLabels are text-muted-foreground text-sm. Values are font-medium. Total row is text-lg font-semibold.
Workflow notes sidebarCard with text-sm text-muted-foregroundshadcnReference material, not primary content. Small text, muted colors.
Confirmation dialogsAlertDialogshadcnFor destructive actions (delete quote, reject quote).
Toast notificationsSonner / toast()shadcnAfter save, approve, reject, delete. Brief confirmation messages.
Quote document headerCard with logo image + company nameshadcnBrand logo from domain.md assets. Placeholder address below.
Quote document pricing tableTable with TableFootershadcnClean itemized rows. Footer shows subtotal. Totals block below with large bold total.
Quote notesTextareashadcnPlain text editing for internal notes.

Config Schema

See references/config-schema.md for full TypeScript interfaces. Summary:

config
├── app                    ← Branding, theme (from brandfetch)
├── sections[]             ← Fixed (from this skill, above)
├── data
│   ├── products[]         ← Catalog of available products with options
│   ├── lineItems[]        ← Current quote contents (starts empty)
│   └── quoteSettings      ← Currency, tax, terms, markup
├── rules[]                ← Business rules (dependencies, validations)
└── roles[]                ← User roles and permissions

Deterministic Mapping Rules

The Builder Agent follows these rules mechanically to transform DOMAIN.md into config.json. No reasoning, no interpretation — execute the rules.

Entity → Configurable Item Mapping

FOR EACH entity in DOMAIN.md Entity Registry:

  — PRODUCT DOMAINS (entities have prices, options, accessories):
  WHERE entity description suggests a sellable product, equipment, service, or part:
    → CREATE config.data.products[] entry
    → SET id = slugify(entity name)
    → SET name = entity name (exact, from DOMAIN.md)
    → SET category = entity parent grouping or category (from Entity Registry or Relationship Map)
    → SET basePrice = entity price if stated (number, no currency symbol)
    → SET pricingSource = determine from context:
        IF "price list" or "vendor list" or "catalog price" → "catalog"
        IF "get a quote from vendor" or "depends on specs" → "vendor_rfq"
        IF "we know our cost and mark it up" → "cost_plus"
        IF no pricing discussed → "tbd"
    → SET options[] = from entity's "what varies" attributes:
        FOR EACH variation mentioned (size, model, capacity, material, type):
          → CREATE option entry with id, label, type, choices

  — CALCULATOR / INTAKE DOMAINS (entities are input fields with rules):
  WHERE entity description suggests an input field, data point, or parameter:
    → CREATE config.data.inputSections[] entry
    → GROUP related inputs into sections (e.g., "Personal details," "Financial details")
    → SET field.id = slugify(field name)
    → SET field.label = field name (exact, from DOMAIN.md)
    → SET field.type = infer from context:
        IF enumerated values → "select" with options
        IF yes/no → "checkbox"
        IF date → "date"
        IF number (income, amount, duration) → "number"
        IF free text → "text"
    → SET field.required = true if DOMAIN.md marks it as mandatory
    → SET field.validation = from business rules (min/max, format, constraints)

  WHERE entity has NO price, NO options, NO input role, and is NOT referenced:
    → SKIP — it's probably not configurable
    → ADD to Open Questions: "Is [entity] an input or a quotable item?"

Relationship → Rule Mapping

FOR EACH relationship in DOMAIN.md Relationship Map:

  IF relationship type = "requires":
    → CREATE config.rules[] entry
    → SET type = "requires"
    → SET severity = "error"
    → SET trigger = "addItem"
    → SET condition = { "item.category": [source entity category] }
    → SET action = { "suggest": [target entity id] }
    → IF relationship mentions size/model matching:
        → SET action.matchField = the matching attribute
    → SET message = relationship rationale from DOMAIN.md (the "because")
    → IF no rationale captured:
        → SET message = "[source] requires [target] — rationale not captured"

  IF relationship type = "recommends":
    → CREATE config.rules[] entry
    → SET type = "recommends"
    → SET severity = "warning"
    → SET trigger = "addItem"
    → SET condition = { "item.category": [source entity category] }
    → SET action = { "suggest": [target entity id] }
    → SET message = relationship rationale from DOMAIN.md

  IF relationship type = "excludes":
    → CREATE config.rules[] entry
    → SET type = "excludes"
    → SET severity = "error"
    → SET trigger = "addItem"
    → SET condition = { "item.category": [source entity category] }
    → SET action = { "block": [target entity id] }
    → SET message = relationship rationale from DOMAIN.md

Business Rule → Validation Mapping

FOR EACH rule in DOMAIN.md Business Rules:

  IF rule mentions "approval" or "requires authorization":
    → CREATE config.rules[] entry
    → SET type = "validates"
    → SET trigger = the action being gated (e.g., "setTerms", "approveQuote")
    → SET condition = the triggering condition
    → SET action = { "requireApproval": [role] }
    → SET severity = "warning"
    → SET message = rule rationale

  IF rule mentions "cannot" or "must not" or "not allowed":
    → CREATE config.rules[] entry
    → SET type = "validates"
    → SET trigger = the blocked action
    → SET condition = the triggering condition
    → SET action = { "block": true }
    → SET severity = "error"
    → SET message = rule rationale

  IF rule mentions pricing constraint (markup, margin, discount limit):
    → CREATE config.rules[] entry
    → SET type = "computes"
    → SET trigger = "priceCalculation"
    → SET condition = the pricing formula or constraint
    → SET action = { "compute": [formula description] }
    → SET message = rule rationale

State Model → Quote Settings Mapping

IF DOMAIN.md State Models contains payment terms or approval statuses:
  → MAP to config.data.quoteSettings.availableTerms[]
  → SET config.data.quoteSettings.defaultTerms = the default mentioned

IF DOMAIN.md State Models contains quote statuses:
  → MAP to section gating logic
  → The CPQ skill handles this through the fixed section definitions (Configure → Quote → Approve → Quote Document)

Branding → Theme Mapping

→ SET config.app.companyName = DOMAIN.md Project Overview company name
→ SET config.app.theme.primaryColor = from brandfetch (or fallback "#1a1a2e")
→ SET config.app.theme.accentColor = from brandfetch (or fallback "#3b82f6")
→ SET config.app.theme.logoUrl = from brandfetch
→ SET config.app.theme.mode = "light"

Quote Settings Mapping

→ SET config.data.quoteSettings.currency = from DOMAIN.md (or default "USD")
→ SET config.data.quoteSettings.taxEnabled = true/false based on DOMAIN.md
→ SET config.data.quoteSettings.taxLabel = from DOMAIN.md (e.g., "HST", "GST", "Sales Tax")
→ SET config.data.quoteSettings.taxRate = from DOMAIN.md (decimal, e.g., 0.13 for 13%)
→ SET config.data.quoteSettings.defaultTerms = from DOMAIN.md (e.g., "net30")
→ SET config.data.quoteSettings.markup = from DOMAIN.md (decimal, e.g., 0.375 for 37.5%)
→ SET config.data.quoteSettings.quoteFormat = "itemized" (default, unless DOMAIN.md says otherwise)

Role Mapping

FOR EACH role in DOMAIN.md User Roles:
  → CREATE config.roles[] entry
  → SET id = slugify(role name)
  → SET label = role name (exact, from DOMAIN.md)
  → SET permissions = infer from DOMAIN.md role description:
      IF role creates quotes → ["createQuote", "editQuote", "selectProducts"]
      IF role approves → ["approveQuote", "approveTerms", "viewReports"]
      IF role manages catalog → ["editCatalog", "editPricing"]
      IF role is view-only → ["viewQuotes"]
  → IF permissions cannot be inferred → SET permissions = ["createQuote", "editQuote"] (safe default)

Edge Cases

→ IF entity has no price → SET pricingSource = "tbd", ADD to config metadata openQuestions
→ IF relationship rationale is missing → SET message = "[source] [relationship] [target] — rationale not captured"
→ IF entity cannot be classified as a product → SKIP, ADD to config metadata openQuestions
→ IF DOMAIN.md has Open Questions → COPY to config.metadata.openQuestions for reference
→ IF no roles are defined in DOMAIN.md → CREATE one default role: { id: "user", label: "User", permissions: ["createQuote", "editQuote", "selectProducts"] }
→ IF no pricing information at all → SET quoteSettings.markup = 0, quoteSettings.currency = "USD", ADD note to openQuestions

Business Rule Templates

Common CPQ rule patterns the Builder Agent can use when DOMAIN.md describes rules in natural language:

Product Dependency (hard)

{
  "id": "BR-XXX",
  "type": "requires",
  "trigger": "addItem",
  "condition": { "item.category": "[source_category]" },
  "action": { "suggest": "[target_product_id]", "matchField": "[matching_attribute]" },
  "message": "[rationale from DOMAIN.md]",
  "severity": "error"
}

Product Recommendation (soft)

{
  "id": "BR-XXX",
  "type": "recommends",
  "trigger": "addItem",
  "condition": { "item.category": "[source_category]" },
  "action": { "suggest": "[target_product_id]" },
  "message": "[rationale from DOMAIN.md]",
  "severity": "warning"
}

Product Exclusion

{
  "id": "BR-XXX",
  "type": "excludes",
  "trigger": "addItem",
  "condition": { "item.category": "[source_category]" },
  "action": { "block": "[target_product_id]" },
  "message": "[rationale from DOMAIN.md]",
  "severity": "error"
}

Approval Gate

{
  "id": "BR-XXX",
  "type": "validates",
  "trigger": "[gated_action]",
  "condition": { "[field]": { "$ne": "[default_value]" } },
  "action": { "requireApproval": "[role_id]" },
  "message": "[rationale from DOMAIN.md]",
  "severity": "warning"
}

Price Computation

{
  "id": "BR-XXX",
  "type": "computes",
  "trigger": "priceCalculation",
  "condition": { "item.pricingSource": "cost_plus" },
  "action": { "compute": "sellingPrice = cost * (1 + markup)" },
  "message": "Cost-plus pricing: [markup]% markup applied",
  "severity": "info"
}

Vertical Presets

See references/vertical-presets.md for full details. Summary:

Vertical"Configure" InputsCalculation ModelKey Rule PatternOutput Style
Manufacturing / BOMProducts with options, accessoriesCost-plus or vendor RFQEquipment requires parts/installationItemized quote with scope of work
Wholesale / DistributionCatalog items, bulk goodsPrice list with volume tiersProduct bundles, case/pallet quantitiesItemized quote with quantity breaks
Services / IntegratorEquipment + installation + PMMixed (catalog + labor rates)Equipment requires service, service includes consumablesItemized quote with service schedule
Legal / ComplianceCase details, personal/financial dataGuideline formulas, statutory tablesInput dependencies (field X required when Y = Z)Summary report with calculations + disclaimer
Financial / InsuranceCoverage details, risk factorsRate tables, actuarial formulasEligibility rules, coverage limitsEstimate document with ranges + terms
Assessment / EligibilityPersonal/business detailsScoring models, threshold rulesPass/fail criteria, tiered outcomesAssessment report with recommendations

The Builder Agent uses the vertical preset as a starting point, then overrides with specifics from DOMAIN.md. For product domains, "Configure" means product selection. For calculator/intake domains, "Configure" means guided data entry.


Mapping Log

After executing the mapping rules, the Builder Agent should produce a brief mapping log as an audit trail:

## Mapping Log

**Skill:** cpq-builder v4.2
**DOMAIN.md:** [company name]
**Vertical preset:** [manufacturing / wholesale / services / legal / financial / assessment / none]

### Products mapped: [N]
- [entity name] → config.data.products[0] ([pricingSource], [N] options)
- [entity name] → config.data.products[1] ([pricingSource], [N] options)

### Rules mapped: [N]
- BR-001: [source] requires [target] → severity: error
- BR-002: [source] recommends [target] → severity: warning

### Skipped entities: [N]
- [entity name] — not a quotable product (added to openQuestions)

### Open questions carried forward: [N]
- [question from DOMAIN.md]

This log is NOT part of the config. It's a separate output that can be reviewed if the config seems wrong.


Reference Files

  • references/config-schema.md — Full TypeScript interfaces for the CPQ config shape
  • references/vertical-presets.md — Detailed presets for Manufacturing, Wholesale, and Services
  • references/example-config.json — Fully populated Total Water example config

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

playwright-interactive-sandbox

No summary provided by upstream source.

Repository SourceNeeds Review
General

frontend-design

No summary provided by upstream source.

Repository SourceNeeds Review
Web3

crypto-report

No summary provided by upstream source.

Repository SourceNeeds Review
1.5K-aahl
Web3

agentwallet

No summary provided by upstream source.

Repository SourceNeeds Review