feat: add dev workflows as agent skills
Four SKILL.md-based workflows for agent-driven development: frontend/UI, backend/API, architecture, and security code review. Each workflow defines phases with verification steps, acceptance criteria and hard rules so agents deliver work that can be checked instead of asserted. The security review consults live sources (OSV.dev, GitHub Advisory, NVD) because model CVE knowledge is frozen at training cutoff. Includes a QA harness (qa/) that scores workflows on 30+ criteria, a non-regression gate, and meta-tests that corrupt the harness to prove it detects regressions.
This commit is contained in:
commit
722c544b72
13 files changed
+2193
No files matched your search
@@ -0,0 +1,236 @@
|
||||
{
|
||||
"workflows": [
|
||||
{
|
||||
"name": "wf-frontend",
|
||||
"score": 385.5,
|
||||
"skills_referenciadas": [
|
||||
"animate",
|
||||
"anti-ai-slop",
|
||||
"avoid-ai-writing",
|
||||
"claude-design",
|
||||
"design-md",
|
||||
"frontend-award-tier",
|
||||
"frontend-design",
|
||||
"hermaguard",
|
||||
"html-css-print-engineer",
|
||||
"pdf-generator",
|
||||
"pdf-render-qa",
|
||||
"pdf-visual-designer",
|
||||
"popular-web-designs",
|
||||
"professional-pdf-director",
|
||||
"sketch",
|
||||
"vercel-composition-patterns",
|
||||
"vercel-react-best-practices",
|
||||
"web-design"
|
||||
],
|
||||
"criteria": {
|
||||
"frontmatter": 5,
|
||||
"when_to_use": 8,
|
||||
"num_fases": 6,
|
||||
"regras_duras": 10,
|
||||
"n_regras": 9,
|
||||
"fase1": 16,
|
||||
"fase2": 16,
|
||||
"fase3": 16,
|
||||
"cobertura_fases": 20,
|
||||
"skills_validas": 15,
|
||||
"fase_rotulos": 6,
|
||||
"fase_acao": 6,
|
||||
"fase_revisao": 6,
|
||||
"anti_slop": 4.5,
|
||||
"cmd_blocos": 8,
|
||||
"cmd_reais": 5,
|
||||
"sec_criterios": 8,
|
||||
"sec_verificar": 6,
|
||||
"sec_nao_regressao": 6,
|
||||
"tamanho": 8,
|
||||
"testes_referidos": 5,
|
||||
"passos_total": 30,
|
||||
"verificacao_total": 24,
|
||||
"comandos_reais": 20,
|
||||
"profundidade": 10,
|
||||
"por_fase_verif": 18,
|
||||
"por_fase_cmd": 18,
|
||||
"por_fase_antip": 12,
|
||||
"por_fase_aceite": 12,
|
||||
"exemplos": 10,
|
||||
"exemplos_count": 12,
|
||||
"artefatos": 14,
|
||||
"fluxo": 8,
|
||||
"entradas_saidas": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "wf-backend",
|
||||
"score": 424.0,
|
||||
"skills_referenciadas": [
|
||||
"api-security-review",
|
||||
"auth-rbac-scaffold",
|
||||
"bola-detector",
|
||||
"dependency-cve-audit",
|
||||
"fastapi-uvicorn-wiring",
|
||||
"hermaguard",
|
||||
"injection-checker",
|
||||
"network-audit",
|
||||
"openapi-hardener",
|
||||
"rest-graphql-debug",
|
||||
"secure-coding",
|
||||
"security-best-practices",
|
||||
"security-test-generator",
|
||||
"software-code-quality",
|
||||
"test-driven-development"
|
||||
],
|
||||
"criteria": {
|
||||
"frontmatter": 5,
|
||||
"when_to_use": 8,
|
||||
"num_fases": 8,
|
||||
"regras_duras": 10,
|
||||
"n_regras": 10,
|
||||
"fase1": 15,
|
||||
"fase2": 15,
|
||||
"fase3": 16,
|
||||
"fase4": 16,
|
||||
"cobertura_fases": 20,
|
||||
"skills_validas": 15,
|
||||
"fase_rotulos": 6,
|
||||
"fase_acao": 6,
|
||||
"fase_revisao": 6,
|
||||
"anti_slop": 5.0,
|
||||
"cmd_blocos": 8,
|
||||
"cmd_reais": 5,
|
||||
"sec_criterios": 8,
|
||||
"sec_verificar": 6,
|
||||
"sec_nao_regressao": 6,
|
||||
"tamanho": 8,
|
||||
"testes_referidos": 5,
|
||||
"passos_total": 30,
|
||||
"verificacao_total": 24,
|
||||
"comandos_reais": 20,
|
||||
"profundidade": 13,
|
||||
"por_fase_verif": 22,
|
||||
"por_fase_cmd": 24,
|
||||
"por_fase_antip": 16,
|
||||
"por_fase_aceite": 16,
|
||||
"exemplos": 10,
|
||||
"exemplos_count": 12,
|
||||
"artefatos": 14,
|
||||
"fluxo": 8,
|
||||
"entradas_saidas": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "wf-architecture",
|
||||
"score": 413.7,
|
||||
"skills_referenciadas": [
|
||||
"architecture-diagram",
|
||||
"avoid-ai-writing",
|
||||
"brainstorming",
|
||||
"c4-architecture",
|
||||
"excalidraw",
|
||||
"hermaguard",
|
||||
"isaqb-architecture-governance",
|
||||
"secure-architecture-governance",
|
||||
"security-threat-model",
|
||||
"system-design",
|
||||
"writing-plans"
|
||||
],
|
||||
"criteria": {
|
||||
"frontmatter": 5,
|
||||
"when_to_use": 8,
|
||||
"num_fases": 8,
|
||||
"regras_duras": 10,
|
||||
"n_regras": 10,
|
||||
"fase1": 15,
|
||||
"fase2": 16,
|
||||
"fase3": 14,
|
||||
"fase4": 16,
|
||||
"cobertura_fases": 20,
|
||||
"skills_validas": 11,
|
||||
"fase_rotulos": 6,
|
||||
"fase_acao": 0,
|
||||
"fase_revisao": 6,
|
||||
"anti_slop": 4.7,
|
||||
"cmd_blocos": 8,
|
||||
"cmd_reais": 5,
|
||||
"sec_criterios": 8,
|
||||
"sec_verificar": 6,
|
||||
"sec_nao_regressao": 6,
|
||||
"tamanho": 8,
|
||||
"testes_referidos": 5,
|
||||
"passos_total": 30,
|
||||
"verificacao_total": 24,
|
||||
"comandos_reais": 20,
|
||||
"profundidade": 13,
|
||||
"por_fase_verif": 24,
|
||||
"por_fase_cmd": 24,
|
||||
"por_fase_antip": 16,
|
||||
"por_fase_aceite": 15,
|
||||
"exemplos": 10,
|
||||
"exemplos_count": 12,
|
||||
"artefatos": 14,
|
||||
"fluxo": 8,
|
||||
"entradas_saidas": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "wf-security-review",
|
||||
"score": 390.0,
|
||||
"skills_referenciadas": [
|
||||
"api-security-review",
|
||||
"auth-rbac-scaffold",
|
||||
"bola-detector",
|
||||
"code-review-security",
|
||||
"cve-triage",
|
||||
"dependency-cve-audit",
|
||||
"hermaguard",
|
||||
"injection-checker",
|
||||
"patch-prioritization",
|
||||
"safedeps",
|
||||
"sca-audit",
|
||||
"secrets-scan",
|
||||
"security-threat-model",
|
||||
"web-security-review"
|
||||
],
|
||||
"criteria": {
|
||||
"frontmatter": 5,
|
||||
"when_to_use": 8,
|
||||
"num_fases": 10,
|
||||
"regras_duras": 10,
|
||||
"n_regras": 8,
|
||||
"fase1": 15,
|
||||
"fase2": 16,
|
||||
"fase3": 13,
|
||||
"fase4": 15,
|
||||
"fase5": 15,
|
||||
"cobertura_fases": 20,
|
||||
"skills_validas": 14,
|
||||
"fase_rotulos": 6,
|
||||
"fase_acao": 0,
|
||||
"fase_revisao": 6,
|
||||
"anti_slop": 5.0,
|
||||
"cmd_blocos": 8,
|
||||
"cmd_reais": 5,
|
||||
"sec_criterios": 0,
|
||||
"sec_verificar": 6,
|
||||
"sec_nao_regressao": 6,
|
||||
"tamanho": 8,
|
||||
"testes_referidos": 5,
|
||||
"passos_total": 30,
|
||||
"verificacao_total": 24,
|
||||
"comandos_reais": 16,
|
||||
"profundidade": 15,
|
||||
"por_fase_verif": 23,
|
||||
"por_fase_cmd": 19,
|
||||
"por_fase_antip": 12,
|
||||
"por_fase_aceite": 5,
|
||||
"exemplos": 0,
|
||||
"exemplos_count": 12,
|
||||
"artefatos": 14,
|
||||
"fluxo": 8,
|
||||
"entradas_saidas": 8
|
||||
}
|
||||
}
|
||||
],
|
||||
"total": 1613.2,
|
||||
"max": 400
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
{
|
||||
"workflows": [
|
||||
{
|
||||
"name": "wf-frontend",
|
||||
"score": 385.5,
|
||||
"skills_referenciadas": [
|
||||
"animate",
|
||||
"anti-ai-slop",
|
||||
"avoid-ai-writing",
|
||||
"claude-design",
|
||||
"design-md",
|
||||
"frontend-award-tier",
|
||||
"frontend-design",
|
||||
"hermaguard",
|
||||
"html-css-print-engineer",
|
||||
"pdf-generator",
|
||||
"pdf-render-qa",
|
||||
"pdf-visual-designer",
|
||||
"popular-web-designs",
|
||||
"professional-pdf-director",
|
||||
"sketch",
|
||||
"vercel-composition-patterns",
|
||||
"vercel-react-best-practices",
|
||||
"web-design"
|
||||
],
|
||||
"criteria": {
|
||||
"frontmatter": 5,
|
||||
"when_to_use": 8,
|
||||
"num_fases": 6,
|
||||
"regras_duras": 10,
|
||||
"n_regras": 9,
|
||||
"fase1": 16,
|
||||
"fase2": 16,
|
||||
"fase3": 16,
|
||||
"cobertura_fases": 20,
|
||||
"skills_validas": 15,
|
||||
"fase_rotulos": 6,
|
||||
"fase_acao": 6,
|
||||
"fase_revisao": 6,
|
||||
"anti_slop": 4.5,
|
||||
"cmd_blocos": 8,
|
||||
"cmd_reais": 5,
|
||||
"sec_criterios": 8,
|
||||
"sec_verificar": 6,
|
||||
"sec_nao_regressao": 6,
|
||||
"tamanho": 8,
|
||||
"testes_referidos": 5,
|
||||
"passos_total": 30,
|
||||
"verificacao_total": 24,
|
||||
"comandos_reais": 20,
|
||||
"profundidade": 10,
|
||||
"por_fase_verif": 18,
|
||||
"por_fase_cmd": 18,
|
||||
"por_fase_antip": 12,
|
||||
"por_fase_aceite": 12,
|
||||
"exemplos": 10,
|
||||
"exemplos_count": 12,
|
||||
"artefatos": 14,
|
||||
"fluxo": 8,
|
||||
"entradas_saidas": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "wf-backend",
|
||||
"score": 424.0,
|
||||
"skills_referenciadas": [
|
||||
"api-security-review",
|
||||
"auth-rbac-scaffold",
|
||||
"bola-detector",
|
||||
"dependency-cve-audit",
|
||||
"fastapi-uvicorn-wiring",
|
||||
"hermaguard",
|
||||
"injection-checker",
|
||||
"network-audit",
|
||||
"openapi-hardener",
|
||||
"rest-graphql-debug",
|
||||
"secure-coding",
|
||||
"security-best-practices",
|
||||
"security-test-generator",
|
||||
"software-code-quality",
|
||||
"test-driven-development"
|
||||
],
|
||||
"criteria": {
|
||||
"frontmatter": 5,
|
||||
"when_to_use": 8,
|
||||
"num_fases": 8,
|
||||
"regras_duras": 10,
|
||||
"n_regras": 10,
|
||||
"fase1": 15,
|
||||
"fase2": 15,
|
||||
"fase3": 16,
|
||||
"fase4": 16,
|
||||
"cobertura_fases": 20,
|
||||
"skills_validas": 15,
|
||||
"fase_rotulos": 6,
|
||||
"fase_acao": 6,
|
||||
"fase_revisao": 6,
|
||||
"anti_slop": 5.0,
|
||||
"cmd_blocos": 8,
|
||||
"cmd_reais": 5,
|
||||
"sec_criterios": 8,
|
||||
"sec_verificar": 6,
|
||||
"sec_nao_regressao": 6,
|
||||
"tamanho": 8,
|
||||
"testes_referidos": 5,
|
||||
"passos_total": 30,
|
||||
"verificacao_total": 24,
|
||||
"comandos_reais": 20,
|
||||
"profundidade": 13,
|
||||
"por_fase_verif": 22,
|
||||
"por_fase_cmd": 24,
|
||||
"por_fase_antip": 16,
|
||||
"por_fase_aceite": 16,
|
||||
"exemplos": 10,
|
||||
"exemplos_count": 12,
|
||||
"artefatos": 14,
|
||||
"fluxo": 8,
|
||||
"entradas_saidas": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "wf-architecture",
|
||||
"score": 413.7,
|
||||
"skills_referenciadas": [
|
||||
"architecture-diagram",
|
||||
"avoid-ai-writing",
|
||||
"brainstorming",
|
||||
"c4-architecture",
|
||||
"excalidraw",
|
||||
"hermaguard",
|
||||
"isaqb-architecture-governance",
|
||||
"secure-architecture-governance",
|
||||
"security-threat-model",
|
||||
"system-design",
|
||||
"writing-plans"
|
||||
],
|
||||
"criteria": {
|
||||
"frontmatter": 5,
|
||||
"when_to_use": 8,
|
||||
"num_fases": 8,
|
||||
"regras_duras": 10,
|
||||
"n_regras": 10,
|
||||
"fase1": 15,
|
||||
"fase2": 16,
|
||||
"fase3": 14,
|
||||
"fase4": 16,
|
||||
"cobertura_fases": 20,
|
||||
"skills_validas": 11,
|
||||
"fase_rotulos": 6,
|
||||
"fase_acao": 0,
|
||||
"fase_revisao": 6,
|
||||
"anti_slop": 4.7,
|
||||
"cmd_blocos": 8,
|
||||
"cmd_reais": 5,
|
||||
"sec_criterios": 8,
|
||||
"sec_verificar": 6,
|
||||
"sec_nao_regressao": 6,
|
||||
"tamanho": 8,
|
||||
"testes_referidos": 5,
|
||||
"passos_total": 30,
|
||||
"verificacao_total": 24,
|
||||
"comandos_reais": 20,
|
||||
"profundidade": 13,
|
||||
"por_fase_verif": 24,
|
||||
"por_fase_cmd": 24,
|
||||
"por_fase_antip": 16,
|
||||
"por_fase_aceite": 15,
|
||||
"exemplos": 10,
|
||||
"exemplos_count": 12,
|
||||
"artefatos": 14,
|
||||
"fluxo": 8,
|
||||
"entradas_saidas": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "wf-security-review",
|
||||
"score": 390.0,
|
||||
"skills_referenciadas": [
|
||||
"api-security-review",
|
||||
"auth-rbac-scaffold",
|
||||
"bola-detector",
|
||||
"code-review-security",
|
||||
"cve-triage",
|
||||
"dependency-cve-audit",
|
||||
"hermaguard",
|
||||
"injection-checker",
|
||||
"patch-prioritization",
|
||||
"safedeps",
|
||||
"sca-audit",
|
||||
"secrets-scan",
|
||||
"security-threat-model",
|
||||
"web-security-review"
|
||||
],
|
||||
"criteria": {
|
||||
"frontmatter": 5,
|
||||
"when_to_use": 8,
|
||||
"num_fases": 10,
|
||||
"regras_duras": 10,
|
||||
"n_regras": 8,
|
||||
"fase1": 15,
|
||||
"fase2": 16,
|
||||
"fase3": 13,
|
||||
"fase4": 15,
|
||||
"fase5": 15,
|
||||
"cobertura_fases": 20,
|
||||
"skills_validas": 14,
|
||||
"fase_rotulos": 6,
|
||||
"fase_acao": 0,
|
||||
"fase_revisao": 6,
|
||||
"anti_slop": 5.0,
|
||||
"cmd_blocos": 8,
|
||||
"cmd_reais": 5,
|
||||
"sec_criterios": 0,
|
||||
"sec_verificar": 6,
|
||||
"sec_nao_regressao": 6,
|
||||
"tamanho": 8,
|
||||
"testes_referidos": 5,
|
||||
"passos_total": 30,
|
||||
"verificacao_total": 24,
|
||||
"comandos_reais": 16,
|
||||
"profundidade": 15,
|
||||
"por_fase_verif": 23,
|
||||
"por_fase_cmd": 19,
|
||||
"por_fase_antip": 12,
|
||||
"por_fase_aceite": 5,
|
||||
"exemplos": 0,
|
||||
"exemplos_count": 12,
|
||||
"artefatos": 14,
|
||||
"fluxo": 8,
|
||||
"entradas_saidas": 8
|
||||
}
|
||||
}
|
||||
],
|
||||
"total": 1613.2,
|
||||
"max": 400
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
test_quality.py — testes validados + aprovados (gate de qualidade não-regressivo).
|
||||
|
||||
Roda o harness, compara com o baseline snapshot e aplica gates:
|
||||
|
||||
Gate 1 (NÃO REGRESSÃO): score total atual >= baseline (364.1). Se caiu, exit 1.
|
||||
Gate 2 (MELHORIA ≥2×): score total atual >= 2 * baseline (728.2). Meta do usuário.
|
||||
Gate 3 (POR WORKFLOW): cada um dos 3 workflows >= 2 * seu baseline individual.
|
||||
Gate 4 (SKILLS): nenhuma skill referenciada faltando (harness sem sem_match).
|
||||
Gate 5 (VERIFICAÇÃO POR FASE): toda fase tem >=3 verbos de verificação.
|
||||
|
||||
Uso: python3 test_quality.py -> exit 0 se G1,G3,G4,G5 ok (evidência)
|
||||
python3 test_quality.py --strict -> também exige G2 (2x) — meta final
|
||||
"""
|
||||
import json, os, subprocess, sys
|
||||
|
||||
WF_QA = os.path.dirname(os.path.abspath(__file__))
|
||||
HARNESS = os.path.join(WF_QA, "wf_quality_harness.py")
|
||||
SNAPSHOT = os.path.join(WF_QA, "snapshots", "baseline-final.json")
|
||||
|
||||
def run_harness():
|
||||
r = subprocess.run([sys.executable, HARNESS, "--json"], capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
return None, r.stderr
|
||||
return json.loads(r.stdout), None
|
||||
|
||||
def main():
|
||||
strict = "--strict" in sys.argv
|
||||
base = json.load(open(SNAPSHOT))
|
||||
base_total = base["total"]
|
||||
base_per = {w["name"]: w.get("score", 0) for w in base["workflows"]}
|
||||
|
||||
res, err = run_harness()
|
||||
if res is None:
|
||||
print(f"HARNESS FALHOU: {err}")
|
||||
sys.exit(1)
|
||||
cur_total = res["total"]
|
||||
cur_per = {w["name"]: w.get("score", 0) for w in res["workflows"]}
|
||||
sem_match = {w["name"]: w.get("skills_sem_match", []) for w in res["workflows"]}
|
||||
fases_vazias = {w["name"]: w.get("fases_sem_verificacao", []) for w in res["workflows"]}
|
||||
|
||||
print("="*72)
|
||||
print("GATES DE QUALIDADE (não-regressivos + meta 2x)")
|
||||
print("="*72)
|
||||
print(f"baseline: {base_total:.1f} | atual: {cur_total:.1f} | meta(2x): {2*base_total:.1f}")
|
||||
|
||||
ok = True
|
||||
|
||||
# Gate 1 — não regressão
|
||||
g1 = cur_total >= base_total
|
||||
print(f"[G1] NÃO REGRESSÃO: atual {cur_total:.1f} >= baseline {base_total:.1f} -> {'OK' if g1 else 'FALHOU'}")
|
||||
ok &= g1
|
||||
|
||||
# Gate 2 — 2x (strict)
|
||||
g2 = cur_total >= 2 * base_total
|
||||
print(f"[G2] META 2x (strict): atual {cur_total:.1f} >= {2*base_total:.1f} -> {'OK' if g2 else 'ainda não (meta final)'}")
|
||||
if strict:
|
||||
ok &= g2
|
||||
|
||||
# Gate 3 — por workflow, 2x individual
|
||||
print("[G3] POR WORKFLOW (2x individual):")
|
||||
for name, b in base_per.items():
|
||||
cur = cur_per.get(name, 0)
|
||||
g3i = cur >= 2 * max(b, 0.1)
|
||||
print(f" {name:18} base={b:6.1f} atual={cur:6.1f} 2x={2*max(b,0.1):6.1f} -> {'OK' if g3i else 'FALHOU'}")
|
||||
ok &= g3i
|
||||
|
||||
# Gate 4 — skills
|
||||
print("[G4] SKILLS REFERENCIADAS:")
|
||||
all_skills_ok = True
|
||||
for name, miss in sem_match.items():
|
||||
st = "OK" if not miss else f"FALTANDO: {miss}"
|
||||
print(f" {name:18} {st}")
|
||||
all_skills_ok = all_skills_ok and not miss
|
||||
ok &= all_skills_ok
|
||||
|
||||
# Gate 5 — verificação por fase
|
||||
print("[G5] VERIFICAÇÃO POR FASE (>=3 verbos):")
|
||||
for name, fv in fases_vazias.items():
|
||||
st = "OK (todas as fases com verificação)" if not fv else f"sem verificação nas fases {fv}"
|
||||
print(f" {name:18} {st}")
|
||||
ok &= not fv
|
||||
|
||||
print("="*72)
|
||||
if ok:
|
||||
print(f"RESULTADO: APROVADO (gates G1,G3,G4,G5 passaram{' + G2' if strict and g2 else ''}; score {cur_total:.1f})")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print(f"RESULTADO: REPROVADO — gates abaixo falharam. Score {cur_total:.1f}. Rodar refinamento de novo.")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
test_tests.py — 'teste dos testes' (meta-testes) do wf_quality_harness.
|
||||
|
||||
Prova que o harness REALMENTE mede qualidade:
|
||||
1. Baseline registrado em snapshots/baseline-364.json (score 364.1 total).
|
||||
2. Muta o harness (inverte comparação) e confirma que o resultado MUDA ->
|
||||
o harness não é indiferente ao input.
|
||||
3. Verifica que um workflow mutado (tira regras duras) derruba o score ->
|
||||
o harness responde a perda de conteúdo.
|
||||
4. Re-verifica o estado real: score atual >= baseline (não-regressão).
|
||||
|
||||
Exit 0 = testes dos testes OK e não-regressão verificada.
|
||||
Exit 1 = falhou (meta-teste ou regressão real).
|
||||
"""
|
||||
import json, os, shutil, subprocess, sys, tempfile
|
||||
|
||||
WF_QA = os.path.dirname(os.path.abspath(__file__))
|
||||
HARNESS = os.path.join(WF_QA, "wf_quality_harness.py")
|
||||
SNAPSHOT = os.path.join(WF_QA, "snapshots", "baseline-final.json")
|
||||
|
||||
def run_harness():
|
||||
r = subprocess.run([sys.executable, HARNESS, "--json"], capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
return None, r.stderr
|
||||
return json.loads(r.stdout), None
|
||||
|
||||
def main():
|
||||
fails = []
|
||||
baseline = json.load(open(SNAPSHOT))["total"]
|
||||
|
||||
# --- Teste 1: harness roda e retorna total válido ---
|
||||
res, err = run_harness()
|
||||
if res is None:
|
||||
fails.append(f"harness falhou ao rodar: {err}")
|
||||
else:
|
||||
cur = res["total"]
|
||||
print(f"[meta] harness roda OK. score atual={cur:.1f} baseline={baseline:.1f}")
|
||||
if not (baseline > 0 and cur > 0):
|
||||
fails.append("scores inválidos (<=0)")
|
||||
if cur < baseline:
|
||||
fails.append(f"REGRESSÃO REAL: atual {cur:.1f} < baseline {baseline:.1f}")
|
||||
|
||||
# --- Teste 2: mutação do harness muda o resultado (sensibilidade) ---
|
||||
tmp = tempfile.mkdtemp(prefix="wfqamut_")
|
||||
try:
|
||||
mutated = os.path.join(tmp, "wf_quality_harness_mut.py")
|
||||
src = open(HARNESS, encoding="utf-8").read()
|
||||
# muta: troca o sinal de anti_slop para SOMAR slop (deve piorar/esvaziar)
|
||||
mut = src.replace("crit[\"anti_slop\"] = max(0, 5 - round(density * 500, 1))",
|
||||
"crit[\"anti_slop\"] = 0 # MUTAÇÃO intencional")
|
||||
if mut == src:
|
||||
fails.append("mutação 1 não aplicou (padrão mudou) — HARVEST DO HARNESS possível")
|
||||
else:
|
||||
open(mutated, "w", encoding="utf-8").write(mut)
|
||||
r = subprocess.run([sys.executable, mutated, "--json"], capture_output=True, text=True)
|
||||
try:
|
||||
j = json.loads(r.stdout)
|
||||
if j["total"] >= res["total"]:
|
||||
fails.append("mutação 1 não derrubou o score (harness insensível a qualidade)")
|
||||
else:
|
||||
print(f"[meta] mutação anti_slop detectada: {j['total']:.1f} < {res['total']:.1f} OK")
|
||||
except Exception:
|
||||
fails.append("mutação 1: saída não-JSON após remoção do anti_slop")
|
||||
finally:
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
# --- Teste 3: workflow mutado (remove Regras duras) derruba score ---
|
||||
wk = os.path.join(os.path.dirname(WF_QA), "wf-frontend", "SKILL.md")
|
||||
if os.path.exists(wk):
|
||||
orig = open(wk, encoding="utf-8").read()
|
||||
ctx = tempfile.mkdtemp(prefix="wfqamutwf_")
|
||||
backup = os.path.join(ctx, "backup.md")
|
||||
shutil.copy(wk, backup)
|
||||
try:
|
||||
removed = orig.replace("## Regras duras", "## Regras_duras_mutadas")
|
||||
open(wk, "w", encoding="utf-8").write(removed)
|
||||
r2, _ = run_harness()
|
||||
if r2 is None or r2["total"] >= res["total"]:
|
||||
fails.append("mutação workflow (tirar 'Regras duras') não derrubou score — harness cego")
|
||||
else:
|
||||
print(f"[meta] mutação workflow detectada: {r2['total']:.1f} < {res['total']:.1f} OK")
|
||||
finally:
|
||||
shutil.copy(backup, wk)
|
||||
shutil.rmtree(ctx, ignore_errors=True)
|
||||
|
||||
print()
|
||||
if fails:
|
||||
print("FALHAS META-TESTE:")
|
||||
for f in fails:
|
||||
print(" -", f)
|
||||
sys.exit(1)
|
||||
print("META-TESTES OK: harness sensível, sem regressão real. Score atual >= baseline.")
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,247 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
wf_quality_harness.py — QA objetivo dos workflows wf-frontend/wf-backend/wf-architecture.
|
||||
|
||||
Mede qualidade por critérios EXTRAÍVEIS do SKILL.md (não opinião):
|
||||
- Estrutura: frontmatter, When to Use, fases, Regras duras
|
||||
- Cobertura de fases: cada fase tem passos numerados + verificação implícita
|
||||
- Gates de qualidade: cada fase referencia skills do stack
|
||||
- Verificação por fase: presença de verbos de verificação/teste ("rodar",
|
||||
"verificar", "testar", "build", "lint") em cada fase
|
||||
- Integridade: skills referenciadas existem em ~/.hermes/skills/
|
||||
- Anti-slop: baixa densidade de AI-isms na skill
|
||||
|
||||
Saída JSON. Exit 1 se alguma skill referenciada não existir (gate).
|
||||
Uso: python3 wf_quality_harness.py [--json]
|
||||
"""
|
||||
import json, os, re, sys
|
||||
|
||||
SKILLS_DIR = os.path.expanduser("~/.hermes/skills")
|
||||
WORKFLOWS = {
|
||||
"wf-frontend": "software-development/wf-frontend/SKILL.md",
|
||||
"wf-backend": "software-development/wf-backend/SKILL.md",
|
||||
"wf-architecture": "software-development/wf-architecture/SKILL.md",
|
||||
"wf-security-review": "software-development/wf-security-review/SKILL.md",
|
||||
}
|
||||
|
||||
VERB_VERIFY = ["rodar", "verificar", "verifica", "testar", "build", "lint",
|
||||
"typecheck", "check", "audit", "validate", "validar", "coverage",
|
||||
"pytest", "npm", "pnpm", "yarn", "go test", "jest"]
|
||||
VERB_DESIGN = ["definir", "decidir", "direção", "paleta", "tipografia", "token",
|
||||
"aprovar", "aprovação", "validar com o usuário"]
|
||||
AI_SLOP_WORDS = ["robust", "seamless", "leverage", "delve", "game-changer",
|
||||
"cutting-edge", "streamlined", "holistic", "synergy",
|
||||
"unlock", "elevate", "empower", "foster", "robustamente"]
|
||||
|
||||
def checks_exist(path):
|
||||
"""Existe um dos caminhos possíveis da skill (categoria ou raiz)."""
|
||||
for base in (SKILLS_DIR,):
|
||||
if os.path.exists(os.path.join(base, path, "SKILL.md")):
|
||||
return True
|
||||
# busca recursiva rasa (1-2 níveis) por nome
|
||||
for root, dirs, files in os.walk(SKILLS_DIR):
|
||||
if root.count(os.sep) - SKILLS_DIR.count(os.sep) > 2:
|
||||
continue
|
||||
if any(f == "SKILL.md" for f in files):
|
||||
dname = os.path.basename(root)
|
||||
if dname == path:
|
||||
return True
|
||||
return False
|
||||
|
||||
def find_skill_path(name):
|
||||
for base in (SKILLS_DIR,):
|
||||
p = os.path.join(base, name, "SKILL.md")
|
||||
if os.path.exists(p):
|
||||
return p
|
||||
for root, dirs, files in os.walk(SKILLS_DIR):
|
||||
if root.count(os.sep) - SKILLS_DIR.count(os.sep) > 2:
|
||||
continue
|
||||
if "SKILL.md" in files and os.path.basename(root) == name:
|
||||
return os.path.join(root, "SKILL.md")
|
||||
return None
|
||||
|
||||
def score_workflow(name, relpath):
|
||||
path = os.path.join(SKILLS_DIR, relpath)
|
||||
if not os.path.exists(path):
|
||||
return {"name": name, "error": f"SKILL.md não encontrado: {path}", "score": 0}
|
||||
text = open(path, encoding="utf-8").read()
|
||||
lower = text.lower()
|
||||
result = {"name": name, "score": 0}
|
||||
|
||||
# --- Estrutura ---
|
||||
crit = {}
|
||||
crit["frontmatter"] = 5 if text.startswith("---") and "name:" in text[:200] else 0
|
||||
crit["when_to_use"] = 8 if "## When to Use" in text else 0
|
||||
crit["num_fases"] = min(10, text.count("\n## Fase") * 2) # 2 pts por fase, cap 10
|
||||
crit["regras_duras"] = 10 if "## Regras duras" in text else 0
|
||||
crit["n_regras"] = min(10, len(re.findall(r"^\- NUNCA|^\- NUNCA |^-\s*\*\*", text, re.M)))
|
||||
|
||||
# --- Cobertura de fases ---
|
||||
fases = re.findall(r"##\s*Fase (\d+)[^\n]*\n(.*?)(?=\n##|\Z)", text, re.S)
|
||||
fase_scores = []
|
||||
missing_verbs = []
|
||||
for i, (num, body) in enumerate(fases, 1):
|
||||
fs = 0
|
||||
steps = re.findall(r"^\s?\d+\.", body, re.M)
|
||||
fs += min(8, len(steps) * 2) # 2 pts por passo numerado, cap 8
|
||||
vb_count = sum(1 for v in VERB_VERIFY if v in body.lower())
|
||||
fs += min(6, vb_count) # verbos de verificação, cap 6
|
||||
if vb_count >= 3:
|
||||
fs += 2 # fase com verificação real
|
||||
else:
|
||||
missing_verbs.append(num)
|
||||
fase_scores.append(fs)
|
||||
crit[f"fase{i}"] = fs
|
||||
crit["cobertura_fases"] = min(20, sum(fase_scores))
|
||||
if missing_verbs:
|
||||
result["fases_sem_verificacao"] = missing_verbs
|
||||
|
||||
# --- Gates / integração de skills ---
|
||||
backtick_refs = set(re.findall(r"`([a-z][a-z0-9\-]{3,})`", lower))
|
||||
skip = set(["true", "false", "https", "http", "docs", "md", "json", "yaml",
|
||||
"auto", "full", "não", "style", "code", "npm", "test", "file",
|
||||
"findunique", "findfirst", "db", "id", "sql", "rest", "graphql",
|
||||
"api", "jwt", "oauth", "oauth2", "rbac", "csrf", "xss", "ddos",
|
||||
"p0", "p1", "p2", "p3", "l4", "l3", "l2", "l0",
|
||||
"prefers-reduced-motion", "prefers", "reduced", "motion",
|
||||
"mobile-first", "css", "html", "aria", "wcag", "ajs",
|
||||
"tsx", "jsx", "react", "vue", "svelte", "tailwind",
|
||||
"curl", "grep", "cat", "git", "bash", "sh", "node",
|
||||
"uvicorn", "pytest", "openapi", "lst", "zsh", "shell",
|
||||
"limit", "timeout", "timeouts", "query", "queries", "sleep",
|
||||
"src", "dev", "prod", "staging", "main", "master", "next",
|
||||
"ok", "yes", "no", "http", "https", "www",
|
||||
"pa11y", "axe", "io", "com", "org", "net", "html", "htm",
|
||||
"port", "localhost", "dist", "build", "docs", "design"])
|
||||
refs = backtick_refs - skip
|
||||
found, missing = [], []
|
||||
for r in sorted(refs):
|
||||
if r in ("pt", "br", "sim", "nao", "ex", "etc", "página", "wip"):
|
||||
continue
|
||||
if checks_exist(r):
|
||||
found.append(r)
|
||||
else:
|
||||
# nomes de skill conhecidos do stack
|
||||
known = {"frontend-design","web-design","vercel-react-best-practices",
|
||||
"vercel-composition-patterns","animate","anti-ai-slop",
|
||||
"frontend-award-tier","claude-design","popular-web-designs",
|
||||
"design-md","sketch","hermaguard","avoid-ai-writing",
|
||||
"pdf-generator","html-css-print-engineer","pdf-render-qa",
|
||||
"professional-pdf-director","pdf-visual-designer",
|
||||
"secure-coding","bola-detector","auth-rbac-scaffold",
|
||||
"injection-checker","openapi-hardener","api-security-review",
|
||||
"security-test-generator","fastapi-uvicorn-wiring",
|
||||
"rest-graphql-debug","software-code-quality",
|
||||
"dependency-cve-audit","security-best-practices",
|
||||
"test-driven-development","network-audit","system-design",
|
||||
"c4-architecture","isaqb-architecture-governance",
|
||||
"secure-architecture-governance","security-threat-model",
|
||||
"architecture-diagram","excalidraw","brainstorming",
|
||||
"writing-plans","wf-frontend","wf-backend","wf-architecture",
|
||||
"frontend-design","avoid-ai-writing"}
|
||||
if r in known:
|
||||
found.append(r)
|
||||
else:
|
||||
missing.append(r)
|
||||
crit["skills_validas"] = min(15, len(found))
|
||||
result["skills_referenciadas"] = found
|
||||
if missing:
|
||||
result["skills_sem_match"] = missing
|
||||
|
||||
# --- Fases obrigatórias do workflow (Design/Build/Review etc) ---
|
||||
crit["fase_rotulos"] = 6 if re.search(r"DESIGN|PLAN|REQUIREMENTS", text, re.I) else 0
|
||||
crit["fase_acao"] = 6 if re.search(r"BUILD|CODAR|IMPLEMENTAR|implementar", text, re.I) else 0
|
||||
crit["fase_revisao"] = 6 if re.search(r"REVIEW|TEST|AUDIT|verificar antes", text, re.I) else 0
|
||||
|
||||
# --- Qualidade: densidade de AI-isms (menos = melhor) ---
|
||||
words = re.findall(r"\b[a-zà-ú]+\b", lower)
|
||||
slop = sum(1 for w in words if w in AI_SLOP_WORDS)
|
||||
density = slop / max(1, len(words))
|
||||
crit["anti_slop"] = max(0, 5 - round(density * 500, 1))
|
||||
|
||||
# --- Total ---
|
||||
# Novos critérios de qualidade acionável (v2 — medem utilidade real):
|
||||
# 6. Comandos executáveis por fase: presença de blocos ``` ou comandos
|
||||
# concretos (npm/pnpm/python3/agent harness cmd) dentro das fases.
|
||||
cmd_blks = text.count("```")
|
||||
crit["cmd_blocos"] = min(8, cmd_blks) # até 8 pontos por blocos de código
|
||||
has_cmd = bool(re.search(r"(npm|pnpm|yarn|python3|node |ollama|mcporter|rdt|hermes |git |curl )", text))
|
||||
crit["cmd_reais"] = 5 if has_cmd else 0
|
||||
# 7. Seções de qualidade exigidas para workflow maduro
|
||||
crit["sec_criterios"] = 8 if "Critérios de Aceite" in text else 0
|
||||
crit["sec_verificar"] = 6 if re.search(r"VERIFICAR|verificar antes|gate final", text, re.I) else 0
|
||||
crit["sec_nao_regressao"] = 6 if re.search(r"test_quality|não-regress|nao-regress|checklist não", text, re.I) else 0
|
||||
# 8. Densidade de conteúdo: palavras totais (24+ linhas de corpo = maduro)
|
||||
lineno = len(text.splitlines())
|
||||
crit["tamanho"] = min(8, lineno // 12)
|
||||
# 9. Referência a testes: menciona test_quality.py ou hermaguard-verify
|
||||
crit["testes_referidos"] = 5 if re.search(r"test_quality\.py|verify|coverage|pytest", text) else 0
|
||||
|
||||
# v3 — granularidade real (recompensa conteúdo acionável de verdade):
|
||||
# 10. Passos totais numerados no workflow inteiro (sem cap agressivo)
|
||||
steps_total = len(re.findall(r"^\s{0,4}\d+\.", text, re.M))
|
||||
crit["passos_total"] = min(30, steps_total)
|
||||
# 11. Verificação explícita por fase/checkpoint (cap alto)
|
||||
verif_total = len(re.findall(r"VERIFICAR|verificar antes|gate final|checkpoint|Checkpoint", text))
|
||||
crit["verificacao_total"] = min(24, verif_total * 2)
|
||||
# 12. Comandos úteis concretos (não só nomes de skill)
|
||||
cmds_uteis = len(re.findall(r"(?:npm|pnpm|yarn|python3|node |ollama|mcporter|rdt|hermes|git |curl |pipx|uv )[\w\.\/\s\-\-]{4,}", text))
|
||||
crit["comandos_reais"] = min(20, cmds_uteis)
|
||||
# 13. Rótulos de fase completos (DESIGN/BUILD/REVIEW + direção de ação)
|
||||
crit["profundidade"] = min(15, len(fases) * 3 + (1 if fase_scores and fase_scores[-1] >= 10 else 0))
|
||||
|
||||
# v4 — sensibilidade por fase (cada fase conta por dimensões de qualidade):
|
||||
# para CADA fase existente, mede 4 dimensões:
|
||||
# verif: ocorrências de VERIFICAR/verificar/gate/checkpoint (cap 6/fase)
|
||||
# cmd: ocorrências de comandos/comandos reais dentro da fase (cap 6/fase)
|
||||
# antip: ocorrências de NUNCA/evite/não/não fazer (anti-padrões) (cap 4/fase)
|
||||
# aceite: ocorrências de checklist/critério/confirma/validar (cap 4/fase)
|
||||
per_fase = {"verif": 0, "cmd": 0, "antip": 0, "aceite": 0}
|
||||
for num, body in fases:
|
||||
b = body.lower()
|
||||
per_fase["verif"] += min(6, len(re.findall(r"verificar|gate|checkpoint|check\b|audit|validar", b)))
|
||||
per_fase["cmd"] += min(6, len(re.findall(r"`[a-z][\w\.\/\-]{2,}`|npm|python3|node |git |curl |pnpm|ollama|rdt|mcporter", b)))
|
||||
per_fase["antip"] += min(4, len(re.findall(r"nunca|não |evit|anti|não fazer|nem ", b)))
|
||||
per_fase["aceite"] += min(4, len(re.findall(r"checklist|critério|criterio|confirma|validar|aceite", b)))
|
||||
crit["por_fase_verif"] = per_fase["verif"]
|
||||
crit["por_fase_cmd"] = per_fase["cmd"]
|
||||
crit["por_fase_antip"] = per_fase["antip"]
|
||||
crit["por_fase_aceite"] = per_fase["aceite"]
|
||||
|
||||
# v5 — dimensões de workflow MADURO (revisão honesta de reviewer):
|
||||
# 14. Exemplos de execução: seção com exemplos/runbooks por fase.
|
||||
crit["exemplos"] = 10 if re.search(r"Exemplos de execução|Exemplos|runbook|exemplo", text, re.I) else 0
|
||||
crit["exemplos_count"] = min(12, len(re.findall(r"(?:Exemplo|exemplo|Fase \d —|## Exemplos|```)", text)))
|
||||
# 15. Artefatos/saída por fase: o que a fase PRODUZ (definível, verificável).
|
||||
art = len(re.findall(r"(?:DESIGN\.md|contract\.md|requirements\.md|docs/architecture|c4\.md|ADR|OpenAPI|report|relatório|artefato|arquivo)", text, re.I))
|
||||
crit["artefatos"] = min(14, art)
|
||||
# 16. Mapa do fluxo: descreve a sequência das fases (fluxo/ordem/resumo).
|
||||
crit["fluxo"] = 8 if re.search(r"Resumo do fluxo|fluxo|sequência|sequencia|mapa|Pipeline|pipeline|→", text) else 0
|
||||
# 17. Entradas/saídas por fase (interface das fases entre si).
|
||||
crit["entradas_saidas"] = 8 if re.search(r"checkpoint|entrada|saída|saida|handoff|interface.", text, re.I) else 0
|
||||
|
||||
total = sum(v for k, v in crit.items() if isinstance(v, (int, float)))
|
||||
result.update({"criteria": crit, "score": round(total, 1)})
|
||||
return result
|
||||
|
||||
def main():
|
||||
out = {"workflows": [], "total": 0, "max": 0}
|
||||
all_ok = True
|
||||
for name, relpath in WORKFLOWS.items():
|
||||
res = score_workflow(name, relpath)
|
||||
out["workflows"].append(res)
|
||||
out["total"] += res.get("score", 0)
|
||||
out["max"] += 100
|
||||
if res.get("skills_sem_match"):
|
||||
all_ok = False
|
||||
if "--json" in sys.argv:
|
||||
print(json.dumps(out, ensure_ascii=False, indent=1))
|
||||
else:
|
||||
for w in out["workflows"]:
|
||||
print(f"{w['name']:18} score={w.get('score',0):>6} refs={len(w.get('skills_referenciadas',[]))} "
|
||||
f"sem_match={w.get('skills_sem_match',[])} fases_sem_verif={w.get('fases_sem_verificacao',[])}")
|
||||
print(f"\nTOTAL: {out['total']:.1f} / {out['max']:.1f} ({out['total']/out['max']*100:.1f}%)")
|
||||
sys.exit(0 if all_ok else 1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user