pursuit-brief
SKILL.md
Pursuit Brief Skill
Overview
This skill generates structured 1-page pursuit briefs to support rapid bid/no-bid decisions for RFP opportunities.
Brief Structure
interface PursuitBrief {
rfpId: string;
quickFacts: QuickFacts;
chaseabilityScore: ChaseabilityScore;
summary: string;
whyPursue: string[];
risks: string[];
eligibility: EligibilityChecklist;
recommendedTeam: TeamRecommendation;
winStrategyNotes: string;
}
interface QuickFacts {
source: string;
noticeId: string;
agency: string;
posted: Date;
deadline: Date;
daysRemaining: number;
estValue: string;
location: string;
setAside?: string;
}
interface TeamRecommendation {
technicalLead: boolean;
frontendDevs: number;
backendDevs: number;
fullStackDevs: number;
devOps: boolean;
qa: boolean;
designer: boolean;
}
Template (Markdown)
# Pursuit Brief: {{RFP_TITLE}}
## Quick Facts
| Field | Value |
|-------|-------|
| Source | {{SOURCE}} |
| Notice ID | {{NOTICE_ID}} |
| Agency | {{AGENCY}} |
| Posted | {{POSTED_DATE}} |
| Deadline | {{DEADLINE}} ({{DAYS_REMAINING}} days) |
| Est. Value | {{EST_VALUE}} |
| Location | {{LOCATION}} |
| Set-Aside | {{SET_ASIDE}} |
## Chaseability Score: {{SCORE}}/100
### Score Breakdown
- Technical Relevance: {{TECH_SCORE}}/25
- Scope Fit: {{SCOPE_SCORE}}/20
- Category Focus: {{CATEGORY_SCORE}}/15
- Client Profile: {{CLIENT_SCORE}}/15
- Logistics: {{LOGISTICS_SCORE}}/15
- Skill Alignment: {{SKILL_SCORE}}/10
## Opportunity Summary
{{SUMMARY}}
## Why Pursue
- {{STRENGTH_1}}
- {{STRENGTH_2}}
- {{STRENGTH_3}}
## Risks & Concerns
- {{RISK_1}}
- {{RISK_2}}
- {{RISK_3}}
## Eligibility Status
- [{{US_ORG_STATUS}}] US Organization Requirement
- [{{CLEARANCE_STATUS}}] Security Clearance
- [{{ONSITE_STATUS}}] Onsite Presence
## Recommended Team
| Role | Count |
|------|-------|
| Technical Lead | 1 |
| Frontend Dev | {{FE_COUNT}} |
| Backend Dev | {{BE_COUNT}} |
| Full-Stack Dev | {{FS_COUNT}} |
| DevOps | {{DEVOPS}} |
| QA Engineer | {{QA}} |
## Win Strategy Notes
{{WIN_STRATEGY}}
---
## Decision
- [ ] **PURSUE** - Strong fit, move to capture
- [ ] **MAYBE** - Needs more investigation
- [ ] **SKIP** - Does not meet criteria
**Decision By:** _________________ **Date:** _________
Generation Logic
Convex Action
// convex/pursuits.ts
import { action } from "./_generated/server";
import { v } from "convex/values";
import { internal } from "./_generated/api";
export const generateBrief = action({
args: { rfpId: v.id("rfps") },
handler: async (ctx, args) => {
// Get RFP and evaluation
const rfp = await ctx.runQuery(internal.rfps.get, { id: args.rfpId });
const evaluation = await ctx.runQuery(internal.evaluations.getByRfp, {
rfpId: args.rfpId,
});
if (!rfp) throw new Error("RFP not found");
if (!evaluation) throw new Error("Evaluate RFP first");
// Build quick facts
const quickFacts: QuickFacts = {
source: rfp.source,
noticeId: rfp.externalId,
agency: extractAgency(rfp),
posted: new Date(rfp.postedDate),
deadline: new Date(rfp.expiryDate),
daysRemaining: Math.ceil(
(rfp.expiryDate - Date.now()) / (1000 * 60 * 60 * 24)
),
estValue: rfp.rawData?.estimatedValue ?? "Not specified",
location: rfp.location,
setAside: rfp.setAside,
};
// Generate AI sections
const aiSections = await generateAISections(rfp, evaluation);
// Infer team needs
const recommendedTeam = inferTeamNeeds(rfp);
// Build brief
const brief: PursuitBrief = {
rfpId: args.rfpId,
quickFacts,
chaseabilityScore: {
overall: evaluation.score,
breakdown: buildBreakdown(evaluation),
},
summary: aiSections.summary,
whyPursue: aiSections.strengths,
risks: aiSections.risks,
eligibility: {
usOrg: evaluation.eligibility.status === "ok" ? "✓" : "!",
clearance: "✓",
onsite: "✓",
},
recommendedTeam,
winStrategyNotes: aiSections.winStrategy,
};
// Save brief to pursuit
await ctx.runMutation(internal.pursuits.saveBrief, {
rfpId: args.rfpId,
brief: JSON.stringify(brief),
});
return brief;
},
});
AI Section Generation
async function generateAISections(
rfp: RFP,
evaluation: Evaluation
): Promise<{
summary: string;
strengths: string[];
risks: string[];
winStrategy: string;
}> {
const prompt = `
Analyze this RFP opportunity for Nebula Logix, a cloud-native software company specializing in serverless architectures, APIs, and workflow systems.
RFP Title: ${rfp.title}
Description: ${rfp.description}
Evaluation Score: ${evaluation.score}/100
Matched Keywords: ${evaluation.criteriaResults
.flatMap((c) => c.matchedKeywords)
.join(", ")}
Provide:
1. A 2-3 sentence summary of what the client needs
2. 3 specific reasons why we should pursue this (based on our strengths)
3. 3 potential risks or concerns
4. Initial win strategy notes (how we differentiate, competition considerations)
Respond with JSON only:
{
"summary": "...",
"strengths": ["...", "...", "..."],
"risks": ["...", "...", "..."],
"winStrategy": "..."
}`;
const response = await callAIProvider(prompt);
return JSON.parse(response);
}
Team Inference
function inferTeamNeeds(rfp: RFP): TeamRecommendation {
const text = `${rfp.title} ${rfp.description}`.toLowerCase();
const needs: TeamRecommendation = {
technicalLead: true,
frontendDevs: 0,
backendDevs: 0,
fullStackDevs: 0,
devOps: false,
qa: false,
designer: false,
};
// Frontend indicators
if (
text.includes("frontend") ||
text.includes("react") ||
text.includes("user interface") ||
text.includes("ui/ux")
) {
needs.frontendDevs = 1;
}
// Backend indicators
if (
text.includes("backend") ||
text.includes("api") ||
text.includes("database") ||
text.includes("server")
) {
needs.backendDevs = 1;
}
// Full-stack or general
if (text.includes("full-stack") || text.includes("full stack")) {
needs.fullStackDevs = 2;
}
// DevOps indicators
if (
text.includes("devops") ||
text.includes("ci/cd") ||
text.includes("deployment") ||
text.includes("infrastructure")
) {
needs.devOps = true;
}
// QA indicators
if (
text.includes("testing") ||
text.includes("qa") ||
text.includes("quality assurance")
) {
needs.qa = true;
}
// Design indicators
if (
text.includes("design") ||
text.includes("ux") ||
text.includes("user experience")
) {
needs.designer = true;
}
// Default if nothing detected
if (needs.frontendDevs + needs.backendDevs + needs.fullStackDevs === 0) {
needs.fullStackDevs = 2;
}
return needs;
}
UI Component
// components/PursuitBriefView.tsx
import { useQuery, useMutation } from "convex/react";
import { api } from "../convex/_generated/api";
export function PursuitBriefView({ rfpId }: { rfpId: Id<"rfps"> }) {
const pursuit = useQuery(api.pursuits.getByRfp, { rfpId });
const generateBrief = useMutation(api.pursuits.generateBrief);
const updateDecision = useMutation(api.pursuits.updateDecision);
const brief = pursuit?.brief ? JSON.parse(pursuit.brief) : null;
if (!brief) {
return (
<div className="p-6 text-center">
<p className="text-muted-foreground mb-4">
No pursuit brief generated yet.
</p>
<button
onClick={() => generateBrief({ rfpId })}
className="px-4 py-2 bg-primary text-primary-foreground rounded"
>
Generate Pursuit Brief
</button>
</div>
);
}
return (
<div className="p-6 max-w-4xl mx-auto space-y-6">
<h1 className="text-2xl font-bold">
Pursuit Brief: {brief.quickFacts.agency}
</h1>
{/* Quick Facts Table */}
<QuickFactsTable facts={brief.quickFacts} />
{/* Score Display */}
<ScoreCard score={brief.chaseabilityScore} />
{/* Summary */}
<section>
<h2 className="text-lg font-semibold mb-2">Summary</h2>
<p className="text-muted-foreground">{brief.summary}</p>
</section>
{/* Why Pursue */}
<section>
<h2 className="text-lg font-semibold mb-2">Why Pursue</h2>
<ul className="list-disc list-inside space-y-1">
{brief.whyPursue.map((item, i) => (
<li key={i} className="text-muted-foreground">{item}</li>
))}
</ul>
</section>
{/* Risks */}
<section>
<h2 className="text-lg font-semibold mb-2">Risks & Concerns</h2>
<ul className="list-disc list-inside space-y-1">
{brief.risks.map((item, i) => (
<li key={i} className="text-muted-foreground">{item}</li>
))}
</ul>
</section>
{/* Decision Buttons */}
<div className="flex gap-4 pt-4 border-t">
<button
onClick={() => updateDecision({ rfpId, decision: "pursue" })}
className="px-6 py-2 bg-success text-white rounded font-medium"
>
PURSUE
</button>
<button
onClick={() => updateDecision({ rfpId, decision: "maybe" })}
className="px-6 py-2 bg-warning text-white rounded font-medium"
>
MAYBE
</button>
<button
onClick={() => updateDecision({ rfpId, decision: "skip" })}
className="px-6 py-2 bg-destructive text-white rounded font-medium"
>
SKIP
</button>
</div>
</div>
);
}
Export Functions
export function exportBriefAsMarkdown(brief: PursuitBrief): string {
return `# Pursuit Brief: ${brief.quickFacts.agency}
## Quick Facts
| Field | Value |
|-------|-------|
| Source | ${brief.quickFacts.source} |
| Notice ID | ${brief.quickFacts.noticeId} |
| Deadline | ${formatDate(brief.quickFacts.deadline)} (${brief.quickFacts.daysRemaining} days) |
| Location | ${brief.quickFacts.location} |
## Chaseability Score: ${brief.chaseabilityScore.overall}/100
## Summary
${brief.summary}
## Why Pursue
${brief.whyPursue.map((s) => `- ${s}`).join("\n")}
## Risks
${brief.risks.map((r) => `- ${r}`).join("\n")}
## Win Strategy
${brief.winStrategyNotes}
`;
}
Weekly Installs
4
Repository
atemndobs/nebula-rfpFirst Seen
Feb 26, 2026
Security Audits
Installed on
github-copilot4
codex4
kimi-cli4
amp4
gemini-cli4
openclaw4