From cappy-toolkit
Phase 3 evidence analysis for CAPPY TAC investigations — analyzes HAR files, log bundles, and support archives using jq and grep, cross-references findings against Cortex docs and similar cases, extracts errors and timelines, and checks the Phase 3 completeness gate.
npx claudepluginhub thelightarchitect/cappy-toolkit --plugin cappy-toolkitThis skill uses the workspace's default tool permissions.
<!-- Copyright (C) 2025-2026 Kevin Francis Tan (github.com/theLightArchitect) | SPDX-License-Identifier: AGPL-3.0-or-later -->
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
Version: 3.0.0
Phase: 3 — Evidence Analysis
MCP Tools: analyze-evidence, har-forensics, log-analytics (primary when MCP server available)
Gate: completeness ≥ 80% (see skills/gate/SKILL.md)
Reasoning Libraries: skills/curator/SKILL.md, skills/gate/SKILL.md, skills/validate/SKILL.md
If this skill is invoked via SP-LOOP-3 (loop re-evidence), the spawn prompt will contain a <loop_context> and <targets> block. Read both before executing any step:
loop_iteration ← current iteration number (use depth: "deep" in loops)
unverified_claim_list ← specific claims from Phase 4 that need evidence
evidence_gaps ← specific gaps identified in Phase 4 that need filling
previous_hypothesis ← the hypothesis that failed — look for evidence that discriminates
Loop evidence strategy (when <targets> block is present):
unverified_claim_list — search ALL available files for evidence that could verify or refute itevidence_gaps — look for the specific file types or log entries that would fill the gapdepth: "deep" on any files that were previously analyzed at depth: "standard"depth: "deep" with no new findings → set evidence_ceiling_hit: trueTrack which files have been analyzed in inv_context.json:
jq '.investigation.analyzed_files' "$CASE_DIR/inv_context.json"
Skip files already in this list at the same depth. After analysis, append new files:
jq '.investigation.analyzed_files += [{"file": "{filename}", "depth": "deep", "iteration": {loop_iteration}}]' \
inv_context.json > tmp.json && mv tmp.json inv_context.json
At the very start of execution, output this exact block:
[ Phase 3 — Evidence ]
Tools: tar extract, jq, grep, websearch.sh
Then before each tool call, output a one-liner:
→ {tool-name} {key parameter}
Execute Phase 3 evidence analysis. Use the MCP analyze-evidence tool when available — it performs compiled forensic processing (bundle decompression, binary parsing, structured log extraction). Fall back to native tools (tar, jq, grep) when MCP is unavailable.
Always run first (ensures extracted/ dir exists before MCP calls):
CASE_DIR="{case_dir}"
if [ ! -d "$CASE_DIR/extracted" ] || [ -z "$(ls -A $CASE_DIR/extracted 2>/dev/null)" ]; then
for bundle in "$CASE_DIR"/*.tar.gz "$CASE_DIR"/evidence/*.tar.gz "$CASE_DIR"/*.tgz; do
[ -f "$bundle" ] && tar -xzf "$bundle" -C "$CASE_DIR/extracted/" 2>&1
done
fi
Primary (MCP available):
// Pass the extracted bundle root directory (where env.log lives).
// The tool auto-detects XSOAR bundles and parses all log files within.
// Passing a directory skips re-extraction (fast path, avoids timeout on large bundles).
call_tool({ operation: "execute", tool: "analyze-evidence",
params: {
paths: [{ path: "{case_dir}/extracted", type: "directory" }],
depth: "{depth}"
}})
Depth options: "standard" (default), "deep" (for P1/P2 or ambiguous findings).
Fallback (native):
CASE_DIR="{case_dir}"
# Extract all tar bundles found
for bundle in "$CASE_DIR"/*.tar.gz "$CASE_DIR"/evidence/*.tar.gz; do
[ -f "$bundle" ] && tar -xzf "$bundle" -C "$CASE_DIR/extracted/" 2>&1
done
# List extracted contents
find "$CASE_DIR/extracted" -type f | sort
Detect environment from extracted files:
env_log=$(find "$CASE_DIR" -name "env.log" -type f 2>/dev/null | head -1)
[ -n "$env_log" ] && grep -E "^(Product|Version|Build|Server Name|Deployment):" "$env_log"
Primary (MCP available):
call_tool({ operation: "execute", tool: "har-forensics",
params: { action: "analyze", file: "{har_file}" }})
Fallback (native):
HAR="{har_file}"
# Entry count and status distribution
jq '.log.entries | length' "$HAR"
jq '[.log.entries[].response.status] | group_by(.) | map({status: .[0], count: length})' "$HAR"
# All HTTP errors (4xx/5xx)
jq '.log.entries[] |
select(.response.status >= 400) |
{
url: .request.url,
status: .response.status,
time: .startedDateTime,
duration_ms: .time,
response_size: .response.bodySize
}' "$HAR"
# Slow requests (>5s)
jq '.log.entries[] | select(.time > 5000) | {url: .request.url, duration_ms: .time}' "$HAR"
# Authentication errors specifically
jq '.log.entries[] | select(.response.status == 401 or .response.status == 403) |
{url: .request.url, status: .response.status, time: .startedDateTime}' "$HAR"
Primary (MCP available):
// IMPORTANT: pass the extracted bundle root directory, NOT an individual .log file.
// The tool auto-detects XSOAR bundles via env.log and parses all log files within.
call_tool({ operation: "execute", tool: "log-analytics",
params: {
file_path: "{case_dir}/extracted",
patterns: ["error", "exception", "timeout", "failed", "fatal", "killed"],
depth: "{depth}"
}})
Fallback (native):
LOG_DIR="$CASE_DIR/extracted"
# Find errors with context (3 lines after each match)
grep -rn --include="*.log" -A 3 -i "error\|exception\|failed\|timeout\|refused\|denied\|killed\|OOM\|heap" "$LOG_DIR" | head -200
# Extract timestamps for timeline
grep -rh --include="*.log" -oP "\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}" "$LOG_DIR" | sort -u
# Search for specific error codes
grep -rn --include="*.log" -E "HTTP [45][0-9]{2}|status.*[45][0-9]{2}" "$LOG_DIR" | head -50
# Java/Python stack traces
grep -rn --include="*.log" -A 5 "Traceback\|java\.lang\." "$LOG_DIR" | head -100
List every error, warning, and key event found. For each finding record:
source_file, line_or_entry, timestamp, severity, raw_messageDo not summarize or interpret. List exactly what was found.
Cross-check observed errors and behavior against documentation, similar cases, and KCS articles. This answers: "Is what I'm seeing expected, documented, or a known issue?"
Cortex documentation — does this error match a documented behavior, known limitation, or deprecated feature?
~/.cappy/tools/web-fallback/websearch.sh "site:docs-cortex.paloaltonetworks.com {key_error_message} {product}" 5
Similar closed TAC cases — has this error pattern been seen and resolved before?
**SF CLI (Salesforce case history — primary)**:
```bash
# Verify sandbox container is running
SANDBOX_OK=$(docker ps --filter name=cappy-client --format "{{.Status}}" 2>/dev/null | grep -c "Up")
if [ "$SANDBOX_OK" -gt 0 ]; then
docker exec cappy-client sf data query \
--query "SELECT CaseNumber, Subject, Product__c, Status, Cause__c, Resolution_Steps__c FROM Case WHERE (Subject LIKE '%{key_error_message}%' OR Description LIKE '%{key_error_message}%') AND Status = 'Closed' LIMIT 5" \
--target-org panw --json 2>/dev/null
fi
If sandbox is unavailable or auth is expired, run ~/.cappy/tools/sandbox-auth.sh to refresh,
or sf org login web -a panw --instance-url https://paloaltonetworks.my.salesforce.com for full re-auth.
Fallback (no sandbox):
mcp__mcp-gateway__mcp_jira__jira_search({
jql: "project IN (XSUP, XSOAR) AND text ~ \"{key_error_message}\" AND status = Closed ORDER BY updated DESC",
fields: "summary,status,resolution",
limit: 5
})
**TAC playbooks and KCS articles** (Confluence):
```javascript
mcp__mcp-gateway__confluence__confluence_search({
query: "{key_error_message} {product}",
limit: 10
})
// Fallback
mcp__mcp-gateway__confluence__confluence_search({
query: "{key_error_message} {product}",
limit: 10
})
Record any findings that explain observed errors — these become SECONDARY evidence and directly elevate pattern correlation confidence.
For each Phase 2 STRONG/PARTIAL pattern: does any evidence finding support or contradict it?
Build pairs: Pattern {id} ↔ {file:line} with relationship supports | contradicts
Flag findings that match NO Phase 2 pattern — these are potential novel issues.
skills/curator/SKILL.md)For EVERY finding you plan to report, verify inline before registering:
<claim id="C-{N}">
STATEMENT: "{what the evidence shows}"
<verify>
Q: What is the exact file and line/entry?
A: {file_path}:{line_number} or {har_file}:entry_{N}
Q: Does reading the file confirm this claim?
A: Read({file_path}) line {N}: "{exact content}" → MATCHES | DOES NOT MATCH
</verify>
STATUS: VERIFIED | REJECTED
</claim>
REJECTED claims do not enter inv_context.json.
Score each evidence type present:
completeness = (types with findings) / (types available) × 100
IF completeness ≥ 80%: gate PASSES.
IF completeness < 80%:
evidence_gaps{
"phase": 3,
"gate_status": "PASS | FAIL",
"completeness_score": 0,
"threshold": 80,
"errors_found": [
{ "file": "...", "line": 0, "message": "...", "severity": "..." }
],
"timeline_events": [
{ "timestamp": "...", "event": "...", "source": "file:line" }
],
"pattern_correlations": [
{ "pattern_id": "P-XXX", "evidence": "file:line", "relationship": "supports | contradicts" }
],
"claims_registered": 0,
"claims_rejected": 0,
"uncited_claims": 0,
"evidence_gaps": [],
"unverified_claims_addressed": [],
"evidence_ceiling_hit": false,
"loop_iteration": 0,
"recommendation": "Proceed to Phase 4 | Gate blocked: {reason}",
"recovery_options": []
}
Do NOT proceed to Phase 4. Return this JSON to Main Claude.