Repository navigation
Conversation
Adds a CompanyProfile record (one row per founder) that grounds every persona reasoning about a customer. Until now Qualifier scored fitScore against an unknown ICP, Strategist picked angles without positioning, and Writer drafted about a product the LLM had to guess at — the only way to convey company info was stuffing it into the prompt textarea on every run. - New /settings/company page with a structured form (companyName, oneLiner, productDescription, ICP, positioning, voiceTone, valueProps, competitors, sourceUrl) plus an "Auto-fill from website" button that scrapes /, /about, /pricing, /product, /features via cheerio + turndown and runs an LLM drafter to populate the form for review. - Workflow-start guard: POST /api/runs returns 409 with missingFields + settingsUrl until companyName, oneLiner, productDescription, and icp are non-empty. Frontend toasts with an "Open settings" action. - Selective per-persona threading via pickCompanyProfileSlice — only the fields each persona's prompt actually references are spliced in. Operational personas (CRM Logger, Slack Digest, Pipeline Reporter, Linear Filer, Scheduler) get nothing, so their token budgets stay lean. - WorkContext.summary now leads with the founder's company so the Conductor + Managers reason about tasks already grounded in who the company is — no Conductor code changes needed. - 8 persona prompts updated to reference input.companyProfile.* with concrete grounding instructions (e.g. Qualifier scores against companyProfile.icp, Writer pairs companyProfile.voiceTone with the existing voice samples). - Setup wizard collects an optional homepage URL and points the founder at /settings/company after install. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a collapsible Company Profile card between ResumePill and PromptInput. Shows the founder at-a-glance whether their profile is complete (the four required fields personas read) and renders the saved fields when it is. Closes the loop with the workflow-start guard at /api/runs: - Empty/incomplete state: amber "N missing" chip + warning banner naming the missing fields, "Open settings" CTA pointing at /settings/company. Same field labels the 409 error returns. - Complete state: emerald "complete" chip + summary chip showing companyName + oneLiner. Expanded view renders one-liner / product / ICP / positioning / value props / competitors / voice-tone / source URL. Long markdown fields collapse to 4 lines with "Show more". Also: - /api/mock/company-profile mirror so the card works under NEXT_PUBLIC_USE_MOCKS=1 (returns makeMockCompanyProfile()). - /settings/company now reads GMAESTRO_COMPANY_URL set by the setup wizard and passes it to CompanyProfileForm as setupUrl, so first-time founders see their homepage URL pre-filled in the Auto-fill input. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Single source of truth for everything front+back share about the profile:
the slice map, the field labels, the per-field length caps, and the canonical
render order all move to lib/shared/company-profile-meta.ts. workflows.ts
re-imports the slice map; types.ts re-exports REQUIRED_COMPANY_PROFILE_FIELDS
for legacy callers.
Behavior changes (founder-visible):
- Settings form renders from the meta — required fields always come first
(companyName → oneLiner → productDescription → icp), then optionals in a
consistent order with the dashboard card.
- Each field shows "READ BY <persona>·<persona>·…" derived from
pickCompanyProfileSlice — operational personas (Scheduler, CRM Logger
etc.) are absent because they don't read the profile.
- Per-field counters: companyName/oneLiner show N/140, valueProps and
competitors show N/8 with red highlighting + inline message when over,
Save button disables when any field exceeds its Zod cap. Stops the
founder from submitting payloads the server will 400.
- Profile-incomplete banner uses human labels ("company name, one-liner,
product description, ICP") instead of camelCase identifiers.
- Partial-save toast names the still-missing fields: "Saved. Still missing:
one-liner, ICP." (was: "Saved (2 required field(s) still empty).").
- Dashboard card sections render in the same order as the settings form,
show the same field labels ("WHAT THE PRODUCT DOES" etc), and carry the
same READ BY tags.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Paragon Review Skipped Hi @sxnnywu! Your Polarity credit balance is insufficient to complete this review. Please visit https://app.paragon.run to finish your review. |
There was a problem hiding this comment.
Pull request overview
Adds a founder “Company profile” feature that is required to run workflows, surfaces it in the dashboard/settings UI, and threads a persona-specific slice of the profile into downstream persona inputs. Also introduces an auto-fill flow that scrapes a company website and uses an LLM to draft the profile fields.
Changes:
- Add company profile persistence (SQLite table + Drizzle schema/migration) and CRUD/scrape API routes, including a workflow-start guard that returns 409 until required fields are filled.
- Add UI for viewing/editing the company profile (settings page + dashboard card + form) and update nav/runs UX to route users to settings when blocked.
- Thread
input.companyProfileslices into persona inputs and update prompts/registry accordingly; add a deterministic scraper + LLM drafter pipeline.
Reviewed changes
Copilot reviewed 32 out of 34 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Locks new dependencies for scraping/HTML→markdown conversion and related transitive deps. |
| package.json | Adds cheerio, turndown, and @types/turndown. |
| lib/ui/hooks/use-company-profile.ts | Client hook to load/hydrate company profile and compute completeness/missing fields. |
| lib/ui/components/top-nav.tsx | Adds “Company” settings link in top nav. |
| lib/ui/components/prompt-input.tsx | Handles 409 “company profile required” by showing toast + linking to settings. |
| lib/ui/components/company-profile-form.tsx | New settings form with validation, save, and scrape auto-fill UX. |
| lib/ui/components/company-profile-card.tsx | New dashboard card summarizing profile + completeness and linking to settings. |
| lib/state/workflows.ts | Injects persona-specific companyProfile slices into task inputs/envelopes. |
| lib/state/work-context.ts | Adds companyProfile into work context and includes formatted profile in summary. |
| lib/state/schema.ts | Adds company_profiles table definition and exported types. |
| lib/state/company-profile.ts | Implements profile get/upsert + completeness check + prompt formatting helper. |
| lib/shared/types.ts | Adds CompanyProfile interface and re-exports required-field list. |
| lib/shared/schemas.ts | Adds Zod schemas for company profile read/update and scrape request. |
| lib/shared/mocks.ts | Adds mock company profile fixture. |
| lib/shared/company-profile-meta.ts | Centralizes profile field metadata, ordering, required keys, and persona slice mapping. |
| lib/personas/registry.ts | Extends persona input schema to accept optional companyProfile record. |
| lib/personas/prompts/writer.md | Documents company profile grounding for Writer persona. |
| lib/personas/prompts/theme-synthesizer.md | Documents company profile grounding for Theme Synthesizer persona. |
| lib/personas/prompts/strategist.md | Documents company profile grounding for Strategist persona. |
| lib/personas/prompts/researcher.md | Documents company profile grounding for Researcher persona. |
| lib/personas/prompts/qualifier.md | Documents company profile grounding for Qualifier persona. |
| lib/personas/prompts/feedback-tagger.md | Documents company profile grounding for Feedback Tagger persona. |
| lib/personas/prompts/brief-writer.md | Documents company profile grounding for Brief Writer persona. |
| lib/personas/prompts/activation.md | Documents company profile grounding for Activation persona. |
| lib/ingest/scrape.ts | Implements deterministic multi-page scraping + HTML→markdown conversion. |
| lib/ingest/draft-profile.ts | Adds LLM drafter to turn scrape bundle into a partial profile draft. |
| drizzle/migrations/0003_company_profile.sql | Adds migration for company_profiles table. |
| bin/gmaestro.ts | Captures optional company URL during setup and prints updated next steps. |
| app/api/runs/route.ts | Adds pre-run guard that blocks runs (409) until profile required fields are filled. |
| app/api/mock/company-profile/route.ts | Adds mock profile API for mock mode. |
| app/api/company-profile/scrape/route.ts | Adds scrape+draft API endpoint for auto-fill. |
| app/api/company-profile/route.ts | Adds GET/PUT API for company profile. |
| app/(dashboard)/settings/company/page.tsx | Adds company profile settings page and uses setup URL fallback. |
| app/(dashboard)/page.tsx | Shows company profile card on dashboard. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+431
to
+432
| * unchanged; passing `null` clears a field. Soft caps mirror the form's | ||
| * help text — over-long values are still accepted but get truncated. |
Comment on lines
+22
to
+33
| const parsed = CompanyProfileScrapeRequestSchema.safeParse(body); | ||
| if (!parsed.success) { | ||
| return NextResponse.json( | ||
| { error: "Invalid request", issues: parsed.error.issues }, | ||
| { status: 400 }, | ||
| ); | ||
| } | ||
|
|
||
| let bundle; | ||
| try { | ||
| bundle = await scrapeCompanySite(parsed.data.url); | ||
| } catch (err) { |
Comment on lines
+67
to
+70
| const pages = await Promise.all( | ||
| WELL_KNOWN_PATHS.map((p) => fetchAndConvert(origin, p, deadline)), | ||
| ); | ||
|
|
Comment on lines
+188
to
+192
| const finalUrl = new URL(res.url); | ||
| const requested = new URL(url); | ||
| const redirectedOffOrigin = finalUrl.origin !== requested.origin; | ||
| const body = redirectedOffOrigin ? "" : await res.text(); | ||
| return { status: res.status, body, redirectedOffOrigin }; |
|
|
||
| - **`input.leadId`** — the lead's local id (e.g. `seed-lead-001`). | ||
| - **`input.item`** — the lead's record from the founder's local store. Always carries `email`, `name`, `company`, `source` (one of `inbound_form`, `trial_signup`, `manual_import`), and `rawMessage` (the lead's actual inbound text — usually the most useful single field for personalization). | ||
| - **`input.companyProfile.{companyName, oneLiner, productDescription, voiceTone}`** — **the founder's own company**. `oneLiner` and `productDescription` tell you what we sell so you can frame the hook around it without inventing. `voiceTone` complements the few-shot voice samples — concrete tone guidance the founder has written down (e.g. "lowercase-first, no hype words, signs as the founder"). Always present. |
…i-c5f8f0 # Conflicts: # app/(dashboard)/page.tsx # lib/shared/mocks.ts # lib/shared/schemas.ts # lib/state/schema.ts
After the pivot to G-stack for blogs landed (PR #18, then merged into this branch via 94d5f86), the 6 new content personas (linkedin/x/reddit-researcher, synthesizer, blog-writer, blog-designer) had no entries in COMPANY_PROFILE_SLICES — meaning the dispatcher was passing them empty companyProfile slices. Each slice is tailored to what the persona reads: - linkedin-researcher / x-researcher / reddit-researcher: companyName, oneLiner, productDescription, icp/competitors (icp for B2B-shaped sites like LinkedIn/Reddit, competitors for X trend research) - synthesizer: full picture except value props/competitors — needs positioning + voiceTone to write an outline that sounds like ours - blog-writer: voice-critical persona, full slice except sourceUrl - blog-designer: just companyName + oneLiner for attribution; brand identity (logo, colors) is future work PERSONAS_BY_FIELD will pick this up automatically (it iterates the slice map at module load), so the form's "Read by X, Y, Z" attribution updates without further changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.