agentleFS
schuliganschuligan/claude-skills-collection/skills/qa-agent/SKILL.md

qa-agent

Skill0 starsChanged 5 months agoMITView on GitHub

Automated quality assurance agent that validates project deliverables (docx, xlsx, pptx) against skill specifications. Use when: 'QA this', 'check the output', 'validate the deliverables', 'run quality checks', 'audit the tracker', 'verify formulas', 'check the deck', 'is this correct', or after ANY skill generates output files. Also auto-trigger after project-planner, epic-creator, status-reporter, meeting-prepper, or email-drafter produces deliverables. This agent catches formula gaps, static values, missing sections, layout issues, security violations, and logic errors before the user sees the output.

9 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use release-manager.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • exzmSkill

    release-manager

    Manages releases as a Release Manager/Tech Lead. Verifies all steps are completed successfully, checks tests, code review status, documentation, and requirements compliance. Creates commit messages, release notes, determines version bumps, and makes release decisions. Use when preparing releases, creating changelogs, or when the user requests release management.

    Also in 3 projects that use this

The file

Show its rules and text

We split this skill into the rules it gives an agent

13 rules. 0 are common, 13 are distinctive, and 0 are very specific to this project.

  • 0

    Common

    Most of the 9 edited versions we read keep these.

  • 13

    Distinctive

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Automatically: after any skill generates .docx, .xlsx, or .pptx files

    line 19
  2. Distinctive:

    On demand: when the user says "QA this" or "check the output"

    line 20
  3. Distinctive:

    On import: when the user uploads files for editing/analysis

    line 21
  4. Distinctive:

    When triggered, find all recently generated files:

    line 460
  5. Distinctive:

    If possible, fix issues automatically:

    line 531
  6. Distinctive:

    If ANY critical issue exists:

    line 541
  7. Distinctive:

    Do NOT present files to the user

    line 542
  8. Distinctive:

    Only deliver when QA passes

    line 545
  9. Distinctive:

    NEVER skip QA. Even if the user says "just send it" — run QA first.

    line 557
  10. Distinctive:

    ALWAYS check formulas programmatically — don't trust descriptions.

    line 558
  11. Distinctive:

    ALWAYS check chart objects exist (shape.has_chart) — don't trust visual inspection alone.

    line 559
  12. Distinctive:

    If QA finds an issue that the generating skill should prevent, flag it for skill improvement too.

    line 560
  13. Distinctive:

    Keep a running defect log so patterns can be identified across runs.

    line 561

“Common” is measured: kept by at least 6 in 10 of the 9 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

QA Agent — Automated Deliverable Validation

You are a ruthless quality gate. Your job is to inspect every output file and catch problems BEFORE the user sees them. You don't trust anything — you verify everything programmatically.

Core Philosophy

  1. Trust nothing, verify everything — If the skill says "formula-driven dashboard," open the file and check every cell.
  2. Fail loudly — A missed defect that reaches the user is worse than a false alarm.
  3. Fix what you can, flag what you can't — Minor issues: fix them silently. Major issues: report and offer to fix.
  4. Regression prevention — Keep a record of past defects so you catch them next time.

When to Run

  • Automatically after any skill generates .docx, .xlsx, or .pptx files
  • On demand when the user says "QA this" or "check the output"
  • On import when the user uploads files for editing/analysis

QA Modules

Module 1: Excel Tracker Validation (.xlsx)

import openpyxl

def qa_tracker(filepath):
    """Full QA audit of project tracker spreadsheet."""
    wb = openpyxl.load_workbook(filepath)
    issues = {"CRITICAL": [], "HIGH": [], "MEDIUM": [], "LOW": []}

    # ─────────────────────────────────────────
    # CHECK 1: Required sheets exist
    # ─────────────────────────────────────────
    required_sheets = [
        "Task Tracker", "Gantt Chart", "Milestones",
        "Risk Register", "Open Questions", "Change Log",
        "Resource Loading", "Dashboard"
    ]
    for sheet_name in required_sheets:
        if sheet_name not in wb.sheetnames:
            issues["CRITICAL"].append(f"Missing required sheet: '{sheet_name}'")

    # ─────────────────────────────────────────
    # CHECK 2: Task Tracker — formula audit
    # ─────────────────────────────────────────
    if "Task Tracker" in wb.sheetnames:
        ws = wb["Task Tracker"]
        headers = [cell.value for cell in ws[1]]

        # Map column letters
        col_map = {h: i+1 for i, h in enumerate(headers) if h}

        formula_required_cols = [
            "End Date", "ES", "EF", "LS", "LF",
            "Total Float", "Free Float", "Critical Path?"
        ]

        for col_name in formula_required_cols:
            if col_name not in col_map:
                issues["HIGH"].append(f"Task Tracker missing column: '{col_name}'")
                continue

            col_idx = col_map[col_name]
            static_count = 0
            formula_count = 0
            total_rows = 0

            for row in ws.iter_rows(min_row=2, min_col=col_idx, max_col=col_idx):
                cell = row[0]
                if cell.value is not None:
                    total_rows += 1
                    if isinstance(cell.value, str) and cell.value.startswith("="):
                        formula_count += 1
                    else:
                        static_count += 1

            if total_rows > 0 and static_count > 0:
                pct_static = static_count / total_rows * 100
                severity = "CRITICAL" if pct_static > 50 else "HIGH" if pct_static > 20 else "MEDIUM"
                issues[severity].append(
                    f"'{col_name}' column: {static_count}/{total_rows} cells are STATIC "
                    f"({pct_static:.0f}%) — should be formulas"
                )

        # CHECK 2b: Every non-first task has a predecessor
        if "Predecessors" in col_map:
            pred_col = col_map["Predecessors"]
            for row_num in range(3, ws.max_row + 1):  # Skip header + first task
                pred_val = ws.cell(row_num, pred_col).value
                task_id = ws.cell(row_num, col_map.get("Task ID", 1)).value
                if task_id and (pred_val is None or str(pred_val).strip() == ""):
                    issues["HIGH"].append(
                        f"Task '{task_id}' (row {row_num}) has NO predecessor — "
                        f"breaks CPM dependency chain"
                    )

        # CHECK 2c: Critical path exists and isn't ALL tasks
        if "Critical Path?" in col_map:
            cp_col = col_map["Critical Path?"]
            critical_count = 0
            total_tasks = 0
            for row in ws.iter_rows(min_row=2, min_col=cp_col, max_col=cp_col):
                if row[0].value is not None:
                    total_tasks += 1
                    # Check computed value (may need to evaluate formula)
                    val = str(row[0].value).upper()
                    if val == "YES" or val == '=IF(S{0}=0,"YES","NO")'.format(row[0].row):
                        critical_count += 1

            if total_tasks > 0:
                if critical_count == 0:
                    issues["CRITICAL"].append("No tasks on critical path — CPM not working")
                elif critical_count == total_tasks:
                    issues["HIGH"].append(
                        f"ALL {total_tasks} tasks marked critical — "
                        f"indicates dependencies are linear chain with no parallel work"
                    )

        # CHECK 2d: Dates make logical sense
        if all(c in col_map for c in ["Start Date", "End Date"]):
            start_col = col_map["Start Date"]
            end_col = col_map["End Date"]
            for row_num in range(2, ws.max_row + 1):
                start = ws.cell(row_num, start_col).value
                end = ws.cell(row_num, end_col).value
                task_id = ws.cell(row_num, col_map.get("Task ID", 1)).value
                if start and end and not isinstance(end, str):
                    # Both are dates
                    try:
                        if end < start:
                            issues["CRITICAL"].append(
                                f"Task '{task_id}' (row {row_num}): End Date < Start Date"
                            )
                    except TypeError:
                        pass

    # ─────────────────────────────────────────
    # CHECK 3: Dashboard — zero static values
    # ─────────────────────────────────────────
    if "Dashboard" in wb.sheetnames:
        ws_dash = wb["Dashboard"]
        static_numbers = 0
        formula_numbers = 0
        for row in ws_dash.iter_rows():
            for cell in row:
                if isinstance(cell.value, (int, float)):
                    static_numbers += 1
                elif isinstance(cell.value, str) and cell.value.startswith("="):
                    formula_numbers += 1

        if static_numbers > 0:
            issues["CRITICAL"].append(
                f"Dashboard has {static_numbers} STATIC numeric values — "
                f"ALL numbers must be formulas referencing other sheets"
            )
        if formula_numbers == 0:
            issues["CRITICAL"].append("Dashboard has ZERO formulas — entirely static")

    # ─────────────────────────────────────────
    # CHECK 4: Risk Register — scoring
    # ─────────────────────────────────────────
    if "Risk Register" in wb.sheetnames:
        ws_risk = wb["Risk Register"]
        risk_count = ws_risk.max_row - 1
        if risk_count < 5:
            issues["HIGH"].append(f"Risk Register has only {risk_count} risks — need at least 5")

    # ─────────────────────────────────────────
    # CHECK 5: Security — no macros, no VBA
    # ─────────────────────────────────────────
    if filepath.endswith(".xlsm"):
        issues["CRITICAL"].append("File is .xlsm (macros enabled) — must be .xlsx")
    if wb.vba_archive:
        issues["CRITICAL"].append("File contains VBA macros — security violation")

    # ─────────────────────────────────────────
    # CHECK 6: Gantt references Task Tracker
    # ─────────────────────────────────────────
    if "Gantt Chart" in wb.sheetnames:
        ws_gantt = wb["Gantt Chart"]
        has_cross_ref = False
        for row in ws_gantt.iter_rows():
            for cell in row:
                if isinstance(cell.value, str) and "'Task Tracker'" in cell.value:
                    has_cross_ref = True
                    break
            if has_cross_ref:
                break
        if not has_cross_ref:
            issues["HIGH"].append(
                "Gantt Chart has no references to Task Tracker sheet — "
                "may not update dynamically"
            )

    return issues

Module 2: Charter Validation (.docx)

from docx import Document

def qa_charter(filepath):
    """Validate project charter document structure and content."""
    doc = Document(filepath)
    issues = {"CRITICAL": [], "HIGH": [], "MEDIUM": [], "LOW": []}
    full_text = "\n".join([p.text for p in doc.paragraphs])

    # Required sections (from SKILL.md — all 15 mandatory)
    required_sections = [
        "Executive Summary",
        "Problem Statement", "Business Case",
        "Goals", "Success Criteria",
        "Scope",
        "RACI",
        "Timeline", "Milestones",
        "Risk",
        "Dependencies",
        "Budget", "Resources",
        "Communication Plan",
        "Change Management",
        "Governance",
        "Quality",
        "Open Questions", "Pending",
        "Approvals"
    ]

    for section in required_sections:
        if section.lower() not in full_text.lower():
            issues["HIGH"].append(f"Missing section keyword: '{section}'")

    # Check for RACI table
    has_raci_table = False
    for table in doc.tables:
        for row in table.rows:
            cells_text = [cell.text.upper() for cell in row.cells]
            if any("R" == c.strip() or "A" == c.strip() for c in cells_text):
                has_raci_table = True
                break

    if not has_raci_table:
        issues["HIGH"].append("No RACI matrix table found in charter")

    # Check risk count
    risk_count = full_text.lower().count("risk")
    # Rough heuristic — a proper risk register has repeated structured entries
    tables_with_risk = 0
    for table in doc.tables:
        header_text = " ".join([cell.text for cell in table.rows[0].cells]).lower()
        if "risk" in header_text and "likelihood" in header_text:
            tables_with_risk += 1
            risk_rows = len(table.rows) - 1
            if risk_rows < 5:
                issues["HIGH"].append(f"Risk table has only {risk_rows} risks — need at least 5")

    if tables_with_risk == 0:
        issues["HIGH"].append("No structured risk register table found")

    # Check for empty PENDING markers
    pending_count = full_text.upper().count("PENDING")
    if pending_count > 0:
        issues["LOW"].append(f"{pending_count} PENDING items found — ensure these have owners and due dates")

    # Security: no macros or external links
    # (docx doesn't typically have these, but check for embedded objects)
    if len(doc.inline_shapes) > 10:
        issues["MEDIUM"].append(f"{len(doc.inline_shapes)} embedded objects — verify none are executables")

    return issues

Module 3: Deck Validation (.pptx)

from pptx import Presentation
from pptx.util import Inches, Pt, Emu

def qa_deck(filepath):
    """Validate kickoff deck for chart quality, layout diversity, and security."""
    prs = Presentation(filepath)
    issues = {"CRITICAL": [], "HIGH": [], "MEDIUM": [], "LOW": []}

    slide_count = len(prs.slides)

    # ─────────────────────────────────────────
    # CHECK 1: Minimum slide count
    # ─────────────────────────────────────────
    if slide_count < 12:
        issues["HIGH"].append(f"Only {slide_count} slides — minimum 12, target 15")

    # ─────────────────────────────────────────
    # CHECK 2: Real chart objects (not shapes)
    # ─────────────────────────────────────────
    chart_slides = []
    chart_types_used = set()
    for i, slide in enumerate(prs.slides):
        for shape in slide.shapes:
            if shape.has_chart:
                chart_slides.append(i + 1)
                chart_type = str(shape.chart.chart_type)
                chart_types_used.add(chart_type)

    if len(chart_slides) < 5:
        issues["CRITICAL"].append(
            f"Only {len(chart_slides)} slides have REAL chart objects — "
            f"need at least 5. Charts on slides: {chart_slides}"
        )
    if len(chart_types_used) < 3:
        issues["HIGH"].append(
            f"Only {len(chart_types_used)} distinct chart types — "
            f"need at least 3 for variety. Types: {chart_types_used}"
        )

    # ─────────────────────────────────────────
    # CHECK 3: Background diversity
    # ─────────────────────────────────────────
    backgrounds = []
    for slide in prs.slides:
        bg = slide.background
        fill = bg.fill
        if fill.type is not None:
            try:
                color = str(fill.fore_color.rgb) if fill.fore_color else "none"
                # Classify as dark or light
                if color != "none":
                    r, g, b = int(color[0:2], 16), int(color[2:4], 16), int(color[4:6], 16)
                    luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255
                    backgrounds.append("dark" if luminance < 0.5 else "light")
                else:
                    backgrounds.append("light")  # default
            except:
                backgrounds.append("unknown")
        else:
            backgrounds.append("light")

    # Check for 3+ consecutive same tone
    for i in range(len(backgrounds) - 2):
        if backgrounds[i] == backgrounds[i+1] == backgrounds[i+2]:
            if backgrounds[i] != "unknown":
                issues["MEDIUM"].append(
                    f"Slides {i+1}-{i+3} all have {backgrounds[i]} backgrounds — "
                    f"should alternate"
                )

    # ─────────────────────────────────────────
    # CHECK 4: Text overflow / overlap detection
    # ─────────────────────────────────────────
    for i, slide in enumerate(prs.slides):
        shapes_with_text = []
        for shape in slide.shapes:
            if shape.has_text_frame:
                shapes_with_text.append({
                    "name": shape.name,
                    "left": shape.left,
                    "top": shape.top,
                    "width": shape.width,
                    "height": shape.height,
                    "text": shape.text_frame.text[:50]
                })

        # Check for overlapping shapes
        for j, s1 in enumerate(shapes_with_text):
            for s2 in shapes_with_text[j+1:]:
                # Simple bounding box overlap check
                if (s1["left"] < s2["left"] + s2["width"] and
                    s1["left"] + s1["width"] > s2["left"] and
                    s1["top"] < s2["top"] + s2["height"] and
                    s1["top"] + s1["height"] > s2["top"]):
                    issues["MEDIUM"].append(
                        f"Slide {i+1}: Possible overlap between "
                        f"'{s1['text']}' and '{s2['text']}'"
                    )

    # ─────────────────────────────────────────
    # CHECK 5: Chart data not empty
    # ─────────────────────────────────────────
    for i, slide in enumerate(prs.slides):
        for shape in slide.shapes:
            if shape.has_chart:
                chart = shape.chart
                for series in chart.series:
                    values = list(series.values)
                    if all(v is None or v == 0 for v in values):
                        issues["CRITICAL"].append(
                            f"Slide {i+1}: Chart has empty/zero data series"
                        )

    # ─────────────────────────────────────────
    # CHECK 6: Security — no embedded objects
    # ─────────────────────────────────────────
    for i, slide in enumerate(prs.slides):
        for shape in slide.shapes:
            if shape.shape_type == 7:  # EMBEDDED_OLE_OBJECT
                issues["CRITICAL"].append(
                    f"Slide {i+1}: Contains embedded OLE object — security risk"
                )

    return issues

Module 4: Cross-Deliverable Consistency

def qa_cross_check(charter_path, tracker_path, deck_path):
    """Verify consistency across all three deliverables."""
    issues = {"CRITICAL": [], "HIGH": [], "MEDIUM": [], "LOW": []}

    # Load all three
    from docx import Document
    from pptx import Presentation
    import openpyxl

    doc = Document(charter_path)
    wb = openpyxl.load_workbook(tracker_path)
    prs = Presentation(deck_path)

    charter_text = "\n".join([p.text for p in doc.paragraphs])

    # CHECK 1: Task count consistency
    if "Task Tracker" in wb.sheetnames:
        ws = wb["Task Tracker"]
        tracker_task_count = ws.max_row - 1
        # Check if deck mentions the same count
        deck_text = ""
        for slide in prs.slides:
            for shape in slide.shapes:
                if shape.has_text_frame:
                    deck_text += shape.text_frame.text + " "

        # Look for task count mention in deck
        if str(tracker_task_count) not in deck_text:
            issues["MEDIUM"].append(
                f"Tracker has {tracker_task_count} tasks but deck doesn't mention this number"
            )

    # CHECK 2: Risk count consistency
    if "Risk Register" in wb.sheetnames:
        tracker_risks = wb["Risk Register"].max_row - 1
        # Count risks in charter
        charter_risk_mentions = charter_text.lower().count("risk")
        if tracker_risks > 0 and charter_risk_mentions < 3:
            issues["MEDIUM"].append(
                f"Tracker has {tracker_risks} risks but charter barely mentions risks"
            )

    # CHECK 3: Project name consistency
    # (verify the same project name appears in all three)

    return issues

Workflow

Step 1: Identify Files to Validate

When triggered, find all recently generated files:

import glob, os
output_dir = "./outputs/"  # adjust to your session/output directory
files = {
    "xlsx": glob.glob(os.path.join(output_dir, "**/*.xlsx"), recursive=True),
    "docx": glob.glob(os.path.join(output_dir, "**/*.docx"), recursive=True),
    "pptx": glob.glob(os.path.join(output_dir, "**/*.pptx"), recursive=True),
}

Step 2: Run All Applicable Modules

For each file, run the matching QA module. Collect all issues.

Step 3: Generate QA Report

═══════════════════════════════════════════════════════
QA REPORT — [Project Name]
Date: [Date] | Files Checked: [count]
Overall Verdict: ✅ PASS / ⚠️ PASS WITH WARNINGS / ❌ FAIL
═══════════════════════════════════════════════════════

CRITICAL ISSUES (must fix before delivery):
  ❌ [Issue description — file — location]
  ❌ [Issue description — file — location]

HIGH ISSUES (should fix):
  ⚠️ [Issue description — file — location]

MEDIUM ISSUES (nice to fix):
  ℹ️ [Issue description — file — location]

LOW ISSUES (informational):
  💡 [Issue description — file — location]

─────────────────────────────────────────────────────
FILE-BY-FILE SUMMARY
─────────────────────────────────────────────────────
📊 project-tracker.xlsx
   Sheets: 8/8 ✅
   Formulas: [X] formula cells, [Y] static cells
   CPM: Critical path has [X] tasks, [Y] with float
   Dashboard: [X] formulas, [Y] static ❌/✅
   Security: No macros ✅

📄 project-charter.docx
   Sections: [X]/15
   RACI table: ✅/❌
   Risks: [X] identified
   PENDING items: [X]

📈 kickoff-deck.pptx
   Slides: [X]
   Real charts: [X] on slides [list]
   Chart types: [X] distinct types
   Background variety: ✅/❌
   Overlaps detected: [X]
   Security: No OLE objects ✅

─────────────────────────────────────────────────────
VERDICT
─────────────────────────────────────────────────────
[PASS / FAIL with reasoning]
[List of auto-fixes applied, if any]
[List of issues requiring user decision]

Step 4: Auto-Fix Minor Issues

If possible, fix issues automatically:

  • Static Dashboard values → Replace with formulas
  • Missing conditional formatting → Add it
  • Title overlap in deck → Adjust positioning
  • Missing predecessor → Add FS to previous task

Report all auto-fixes to the user.

Step 5: Block Delivery if Critical Issues

If ANY critical issue exists:

  1. Do NOT present files to the user
  2. Fix the issues
  3. Re-run QA
  4. Only deliver when QA passes

Scoring

VerdictCriteria
✅ PASS0 Critical, 0 High
⚠️ PASS WITH WARNINGS0 Critical, 1-3 High
❌ FAIL1+ Critical OR 4+ High

Important Rules

  • NEVER skip QA. Even if the user says "just send it" — run QA first.
  • ALWAYS check formulas programmatically — don't trust descriptions.
  • ALWAYS check chart objects exist (shape.has_chart) — don't trust visual inspection alone.
  • If QA finds an issue that the generating skill should prevent, flag it for skill improvement too.
  • Keep a running defect log so patterns can be identified across runs.
  • Security checks are NON-NEGOTIABLE — always check for macros, OLE objects, and external connections.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.