Skip to content
Back to skills

Compliance Matrix

ASecurity

Generate and track compliance matrices for RFP requirements. Use when building requirements tracking features, implementing compliance checklists, or ensuring proposal coverage.

  • 666 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added February 8, 2026
developmenttypescriptgoapisecurityperformance

Works with

  • cli
  • api

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill compliance-matrix --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Compliance Matrix?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Compliance Matrix
[![Security: A β€” Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-compliance-matrix/badge)](https://www.skillsdirectory.com/skills/majiayu000-compliance-matrix)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: compliance-matrix
description: Generate and track compliance matrices for RFP requirements. Use when building requirements tracking features, implementing compliance checklists, or ensuring proposal coverage.
allowed-tools: Read, Grep, Glob
---

# Compliance Matrix Skill

## Overview

This skill generates compliance matrices to track RFP requirements against proposal responses, ensuring complete coverage of all mandatory elements.

## Data Structures

```typescript
interface ComplianceMatrix {
  rfpId: string;
  rfpTitle: string;
  createdAt: Date;
  updatedAt: Date;
  status: "draft" | "in_review" | "complete";
  sections: ComplianceSection[];
  summary: MatrixSummary;
}

interface ComplianceSection {
  id: string;
  name: string;
  requirements: ComplianceRequirement[];
}

interface ComplianceRequirement {
  id: string;
  reference: string;           // RFP section reference
  requirement: string;          // Original requirement text
  type: "mandatory" | "desirable" | "informational";
  category: RequirementCategory;
  responseSection: string;      // Proposal section
  responseText: string;         // Draft response
  evidence: string;             // Supporting evidence
  owner: string;                // Team member responsible
  status: "pending" | "draft" | "review" | "complete" | "n/a";
  notes: string;
}

type RequirementCategory =
  | "technical"
  | "management"
  | "past_performance"
  | "pricing"
  | "certifications"
  | "staffing"
  | "security"
  | "compliance"
  | "other";

interface MatrixSummary {
  totalRequirements: number;
  mandatory: number;
  desirable: number;
  addressed: number;
  pending: number;
  notApplicable: number;
}
```

## Template Output

```markdown
# Compliance Matrix: {{RFP_TITLE}}

**RFP ID:** {{EXTERNAL_ID}}
**Agency:** {{AGENCY}}
**Deadline:** {{DEADLINE}}
**Last Updated:** {{UPDATED_AT}}

## Summary
| Status | Mandatory | Desirable | Total |
|--------|-----------|-----------|-------|
| Complete | {{M_COMPLETE}} | {{D_COMPLETE}} | {{TOTAL_COMPLETE}} |
| Draft | {{M_DRAFT}} | {{D_DRAFT}} | {{TOTAL_DRAFT}} |
| Pending | {{M_PENDING}} | {{D_PENDING}} | {{TOTAL_PENDING}} |
| N/A | {{M_NA}} | {{D_NA}} | {{TOTAL_NA}} |

---

## Section 1: Technical Requirements

| Ref | Requirement | Type | Response | Status | Owner |
|-----|-------------|------|----------|--------|-------|
| 3.1.1 | {{REQ}} | M | {{RESPONSE_SECTION}} | βœ… | {{OWNER}} |

---

## Submission Checklist
- [ ] All mandatory requirements addressed
- [ ] Technical volume complete
- [ ] Past performance complete
- [ ] Pricing complete
- [ ] All forms signed
- [ ] Format requirements met
- [ ] Red team review complete
```

## Requirement Extraction

### Convex Action

```typescript
// convex/compliance.ts
import { action, mutation, query } from "./_generated/server";
import { v } from "convex/values";
import { internal } from "./_generated/api";

export const generateMatrix = action({
  args: { rfpId: v.id("rfps") },
  handler: async (ctx, args) => {
    const rfp = await ctx.runQuery(internal.rfps.get, { id: args.rfpId });
    if (!rfp) throw new Error("RFP not found");

    // Extract requirements using AI
    const requirements = await extractRequirements(rfp);

    // Group into sections
    const sections = groupRequirements(requirements);

    // Create matrix
    const matrix: ComplianceMatrix = {
      rfpId: args.rfpId,
      rfpTitle: rfp.title,
      createdAt: new Date(),
      updatedAt: new Date(),
      status: "draft",
      sections,
      summary: calculateSummary(sections),
    };

    // Save to pursuit
    await ctx.runMutation(internal.pursuits.saveComplianceMatrix, {
      rfpId: args.rfpId,
      matrix: JSON.stringify(matrix),
    });

    return matrix;
  },
});
```

### AI Extraction

```typescript
async function extractRequirements(rfp: RFP): Promise<ComplianceRequirement[]> {
  const prompt = `
Analyze this RFP and extract ALL requirements.

RFP Title: ${rfp.title}
RFP Content: ${rfp.description}

For each requirement, identify:
1. Section reference (use sequential numbering if not available)
2. Exact requirement text
3. Type: mandatory (must/shall/required), desirable (should/may), or informational
4. Category: technical, management, past_performance, pricing, certifications, staffing, security, compliance, other

Respond with JSON array:
[
  {
    "reference": "3.1.1",
    "requirement": "System must support 1000 concurrent users",
    "type": "mandatory",
    "category": "technical"
  }
]`;

  const response = await callAIProvider(prompt);
  const parsed = JSON.parse(response);

  return parsed.map((req: any, index: number) => ({
    id: `req-${index + 1}`,
    reference: req.reference || `${index + 1}`,
    requirement: req.requirement,
    type: req.type,
    category: req.category,
    responseSection: "",
    responseText: "",
    evidence: "",
    owner: "",
    status: "pending" as const,
    notes: "",
  }));
}
```

### Grouping Logic

```typescript
function groupRequirements(
  requirements: ComplianceRequirement[]
): ComplianceSection[] {
  const groups = new Map<string, ComplianceRequirement[]>();

  for (const req of requirements) {
    const existing = groups.get(req.category) || [];
    existing.push(req);
    groups.set(req.category, existing);
  }

  const categoryNames: Record<string, string> = {
    technical: "Technical Requirements",
    management: "Management Requirements",
    past_performance: "Past Performance",
    pricing: "Pricing Requirements",
    certifications: "Certifications & Compliance",
    staffing: "Staffing Requirements",
    security: "Security Requirements",
    compliance: "Regulatory Compliance",
    other: "Other Requirements",
  };

  const order = [
    "technical",
    "management",
    "staffing",
    "past_performance",
    "security",
    "compliance",
    "pricing",
    "certifications",
    "other",
  ];

  return order
    .filter((cat) => groups.has(cat))
    .map((category) => ({
      id: category,
      name: categoryNames[category] || category,
      requirements: groups.get(category)!,
    }));
}
```

## Update Functions

```typescript
// convex/compliance.ts
export const updateRequirement = mutation({
  args: {
    rfpId: v.id("rfps"),
    requirementId: v.string(),
    updates: v.object({
      responseSection: v.optional(v.string()),
      responseText: v.optional(v.string()),
      evidence: v.optional(v.string()),
      owner: v.optional(v.string()),
      status: v.optional(v.string()),
      notes: v.optional(v.string()),
    }),
  },
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("Not authenticated");

    const pursuit = await ctx.db
      .query("pursuits")
      .withIndex("by_rfp", (q) => q.eq("rfpId", args.rfpId))
      .first();

    if (!pursuit?.complianceMatrix) {
      throw new Error("Compliance matrix not found");
    }

    const matrix: ComplianceMatrix = JSON.parse(pursuit.complianceMatrix);

    // Find and update requirement
    for (const section of matrix.sections) {
      const req = section.requirements.find((r) => r.id === args.requirementId);
      if (req) {
        Object.assign(req, args.updates);
        break;
      }
    }

    // Recalculate summary
    matrix.summary = calculateSummary(matrix.sections);
    matrix.updatedAt = new Date();

    await ctx.db.patch(pursuit._id, {
      complianceMatrix: JSON.stringify(matrix),
      updatedAt: Date.now(),
    });

    return matrix;
  },
});

export const getProgress = query({
  args: { rfpId: v.id("rfps") },
  handler: async (ctx, args) => {
    const pursuit = await ctx.db
      .query("pursuits")
      .withIndex("by_rfp", (q) => q.eq("rfpId", args.rfpId))
      .first();

    if (!pursuit?.complianceMatrix) return null;

    const matrix: ComplianceMatrix = JSON.parse(pursuit.complianceMatrix);

    return {
      summary: matrix.summary,
      byCategory: matrix.sections.map((s) => ({
        category: s.name,
        total: s.requirements.length,
        complete: s.requirements.filter((r) => r.status === "complete").length,
        mandatory: s.requirements.filter((r) => r.type === "mandatory").length,
      })),
      lastUpdated: matrix.updatedAt,
    };
  },
});
```

## UI Components

```tsx
// components/ComplianceMatrixView.tsx
export function ComplianceMatrixView({ rfpId }: { rfpId: Id<"rfps"> }) {
  const progress = useQuery(api.compliance.getProgress, { rfpId });
  const generateMatrix = useMutation(api.compliance.generateMatrix);

  if (progress === undefined) return <LoadingSpinner />;

  if (!progress) {
    return (
      <div className="p-6 text-center">
        <p className="text-muted-foreground mb-4">
          No compliance matrix generated yet.
        </p>
        <button
          onClick={() => generateMatrix({ rfpId })}
          className="px-4 py-2 bg-primary text-primary-foreground rounded"
        >
          Generate Compliance Matrix
        </button>
      </div>
    );
  }

  return (
    <div className="p-6 space-y-6">
      {/* Summary Cards */}
      <div className="grid grid-cols-4 gap-4">
        <SummaryCard
          label="Total"
          value={progress.summary.totalRequirements}
          color="blue"
        />
        <SummaryCard
          label="Complete"
          value={progress.summary.addressed}
          color="green"
        />
        <SummaryCard
          label="Pending"
          value={progress.summary.pending}
          color="yellow"
        />
        <SummaryCard
          label="Mandatory"
          value={progress.summary.mandatory}
          color="red"
        />
      </div>

      {/* Progress by Category */}
      <div className="space-y-2">
        {progress.byCategory.map((cat) => (
          <ProgressBar
            key={cat.category}
            label={cat.category}
            current={cat.complete}
            total={cat.total}
          />
        ))}
      </div>
    </div>
  );
}
```

## Export Formats

```typescript
export function exportToMarkdown(matrix: ComplianceMatrix): string {
  // See template above
}

export function exportToCsv(matrix: ComplianceMatrix): string {
  const headers = [
    "Section",
    "Reference",
    "Requirement",
    "Type",
    "Response Section",
    "Status",
    "Owner",
    "Notes",
  ];

  const rows = matrix.sections.flatMap((section) =>
    section.requirements.map((req) => [
      section.name,
      req.reference,
      `"${req.requirement.replace(/"/g, '""')}"`,
      req.type,
      req.responseSection,
      req.status,
      req.owner,
      `"${req.notes.replace(/"/g, '""')}"`,
    ])
  );

  return [headers.join(","), ...rows.map((r) => r.join(","))].join("\n");
}
```

## Status Legend

| Icon | Status | Meaning |
|------|--------|---------|
| βœ… | Complete | Fully addressed with evidence |
| πŸ”Ά | Draft | Response written, needs review |
| ⏳ | Pending | Not yet started |
| βž– | N/A | Not applicable to this proposal |

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.