edusquads-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/.claude/skills/edusquads/SKILL.md +212 -0
  2. package/CLAUDE.md +40 -0
  3. package/README.md +92 -0
  4. package/_edusquads/evidencias/EVIDENCIA-MODELO.md +33 -0
  5. package/_edusquads/memoria/USUARIO-ATIVO.md +42 -0
  6. package/_edusquads/runs/RUN-MODELO.md +50 -0
  7. package/_edusquads/runs/RUNS-INDEX.md +25 -0
  8. package/base/catalogo-dos-160-agentes.md +175 -0
  9. package/base/comandos/edusquads-comandos.md +46 -0
  10. package/base/matriz-mestre-dos-16-squads.md +1147 -0
  11. package/base/matriz-mestre-dos-especialistas.md +579 -0
  12. package/base/modelos/modelo-de-agente.md +62 -0
  13. package/base/modelos/modelo-de-skill.md +55 -0
  14. package/base/paridade-opensquad.md +49 -0
  15. package/base/playbooks/investigacao/COMO-EXECUTAR.md +51 -0
  16. package/base/playbooks/investigacao/ESTRUTURACAO.md +28 -0
  17. package/base/playbooks/investigacao/PLAYBOOK-GERAL.md +34 -0
  18. package/base/playbooks/investigacao/instagram.md +23 -0
  19. package/base/playbooks/investigacao/linkedin.md +23 -0
  20. package/base/playbooks/investigacao/x-twitter.md +23 -0
  21. package/base/playbooks/investigacao/youtube.md +23 -0
  22. package/base/protocolo-memoria-usuario.md +50 -0
  23. package/base/protocolo-playwright-edusquads.md +48 -0
  24. package/base/scripts/edusquads_concluir_investigacao.py +356 -0
  25. package/base/scripts/edusquads_estruturar_coleta.py +237 -0
  26. package/base/scripts/edusquads_investigar.py +279 -0
  27. package/base/visao-geral.md +46 -0
  28. package/bin/edusquads.js +146 -0
  29. package/carrosseis.md +988 -0
  30. package/especialistas/branding/marty-neumeier.md +39 -0
  31. package/especialistas/copy/joanna-wiebe.md +41 -0
  32. package/especialistas/copy/russell-brunson.md +41 -0
  33. package/especialistas/mensagem/donald-miller.md +41 -0
  34. package/especialistas/posicionamento/april-dunford.md +39 -0
  35. package/especialistas/trafego-pago/pedro-sobral.md +41 -0
  36. package/package.json +31 -0
  37. package/pesquisa/web/april-dunford.md +37 -0
  38. package/pesquisa/web/claude-code-comandos.md +30 -0
  39. package/pesquisa/web/donald-miller.md +29 -0
  40. package/pesquisa/web/joanna-wiebe.md +29 -0
  41. package/pesquisa/web/marty-neumeier.md +37 -0
  42. package/pesquisa/web/opensquad.md +23 -0
  43. package/pesquisa/web/pedro-sobral.md +29 -0
  44. package/pesquisa/web/pendentes/biblioteca-pendente.md +20 -0
  45. package/pesquisa/web/russell-brunson.md +30 -0
  46. package/squads/01-estrategia/agentes/arquiteto-de-diferencial.md +62 -0
  47. package/squads/01-estrategia/agentes/auditor-de-coerencia-estrategica.md +62 -0
  48. package/squads/01-estrategia/agentes/especialista-em-posicionamento.md +61 -0
  49. package/squads/01-estrategia/agentes/estrategista-de-categoria.md +60 -0
  50. package/squads/01-estrategia/agentes/estrategista-de-mercado.md +61 -0
  51. package/squads/01-estrategia/agentes/planejador-de-tese.md +60 -0
  52. package/squads/01-estrategia/agentes/priorizador-estrategico.md +61 -0
  53. package/squads/01-estrategia/agentes/revisor-estrategico.md +65 -0
  54. package/squads/01-estrategia/agentes/sintetizador-estrategico.md +62 -0
  55. package/squads/01-estrategia/agentes/tradutor-estrategico-para-squads.md +62 -0
  56. package/squads/01-estrategia/squad.md +70 -0
  57. package/squads/02-pesquisa/agentes/analista-de-concorrencia.md +62 -0
  58. package/squads/02-pesquisa/agentes/analista-de-tendencias.md +60 -0
  59. package/squads/02-pesquisa/agentes/auditor-de-suficiencia-de-pesquisa.md +61 -0
  60. package/squads/02-pesquisa/agentes/bibliotecario-de-evidencias.md +62 -0
  61. package/squads/02-pesquisa/agentes/curador-de-fontes.md +61 -0
  62. package/squads/02-pesquisa/agentes/minerador-de-reviews.md +60 -0
  63. package/squads/02-pesquisa/agentes/organizador-de-insights.md +61 -0
  64. package/squads/02-pesquisa/agentes/pesquisador-de-mercado.md +61 -0
  65. package/squads/02-pesquisa/agentes/pesquisador-de-voz-do-cliente.md +63 -0
  66. package/squads/02-pesquisa/agentes/revisor-de-pesquisa.md +61 -0
  67. package/squads/02-pesquisa/squad.md +68 -0
  68. package/squads/03-copy/agentes/copywriter-de-anuncios.md +65 -0
  69. package/squads/03-copy/agentes/copywriter-de-email.md +65 -0
  70. package/squads/03-copy/agentes/copywriter-de-landing-page.md +66 -0
  71. package/squads/03-copy/agentes/critico-de-conversao.md +65 -0
  72. package/squads/03-copy/agentes/editor-de-copy-de-conversao.md +63 -0
  73. package/squads/03-copy/agentes/especialista-em-cta.md +65 -0
  74. package/squads/03-copy/agentes/especialista-em-headlines.md +63 -0
  75. package/squads/03-copy/agentes/pesquisador-de-mensagem.md +63 -0
  76. package/squads/03-copy/agentes/revisor-chefe-de-copy.md +65 -0
  77. package/squads/03-copy/agentes/roteirista-de-funil.md +63 -0
  78. package/squads/03-copy/skills/estruturar-hook-story-offer.md +61 -0
  79. package/squads/03-copy/squad.md +73 -0
  80. package/squads/04-conteudo/agentes/curador-de-temas.md +60 -0
  81. package/squads/04-conteudo/agentes/especialista-em-reaproveitamento.md +60 -0
  82. package/squads/04-conteudo/agentes/estrategista-de-conteudo.md +61 -0
  83. package/squads/04-conteudo/agentes/organizador-de-calendario.md +61 -0
  84. package/squads/04-conteudo/agentes/otimizador-editorial.md +62 -0
  85. package/squads/04-conteudo/agentes/planejador-editorial.md +61 -0
  86. package/squads/04-conteudo/agentes/redator-de-conteudo-seo.md +61 -0
  87. package/squads/04-conteudo/agentes/redator-social.md +61 -0
  88. package/squads/04-conteudo/agentes/revisor-de-conteudo.md +63 -0
  89. package/squads/04-conteudo/agentes/roteirista-de-conteudo.md +61 -0
  90. package/squads/04-conteudo/squad.md +70 -0
  91. package/squads/05-design/agentes/auditor-de-coerencia-visual.md +60 -0
  92. package/squads/05-design/agentes/designer-de-apresentacoes.md +62 -0
  93. package/squads/05-design/agentes/designer-de-criativos.md +90 -0
  94. package/squads/05-design/agentes/designer-de-landing-page.md +62 -0
  95. package/squads/05-design/agentes/designer-visual.md +72 -0
  96. package/squads/05-design/agentes/diretor-de-arte.md +71 -0
  97. package/squads/05-design/agentes/especialista-em-sistemas-visuais.md +60 -0
  98. package/squads/05-design/agentes/revisor-de-design.md +75 -0
  99. package/squads/05-design/agentes/revisor-de-hierarquia-visual.md +60 -0
  100. package/squads/05-design/agentes/tradutor-de-marca-para-design.md +62 -0
  101. package/squads/05-design/squad.md +67 -0
  102. package/squads/06-branding/agentes/arquiteto-de-diferenciacao.md +62 -0
  103. package/squads/06-branding/agentes/auditor-de-coerencia-de-marca.md +62 -0
  104. package/squads/06-branding/agentes/designer-de-narrativa-de-marca.md +62 -0
  105. package/squads/06-branding/agentes/estrategista-de-marca.md +62 -0
  106. package/squads/06-branding/agentes/guardiao-de-consistencia.md +62 -0
  107. package/squads/06-branding/agentes/guardiao-de-tom.md +62 -0
  108. package/squads/06-branding/agentes/planejador-de-identidade.md +60 -0
  109. package/squads/06-branding/agentes/revisor-de-distincao.md +60 -0
  110. package/squads/06-branding/agentes/revisor-de-marca.md +64 -0
  111. package/squads/06-branding/agentes/unificador-de-linguagem.md +60 -0
  112. package/squads/06-branding/squad.md +67 -0
  113. package/squads/07-ux-experiencia/agentes/analista-de-friccao.md +62 -0
  114. package/squads/07-ux-experiencia/agentes/arquiteto-de-informacao.md +60 -0
  115. package/squads/07-ux-experiencia/agentes/critico-de-jornada.md +62 -0
  116. package/squads/07-ux-experiencia/agentes/especialista-em-microcopy.md +63 -0
  117. package/squads/07-ux-experiencia/agentes/otimizador-de-formularios.md +62 -0
  118. package/squads/07-ux-experiencia/agentes/planejador-de-onboarding.md +62 -0
  119. package/squads/07-ux-experiencia/agentes/revisor-de-clareza-de-interface.md +60 -0
  120. package/squads/07-ux-experiencia/agentes/revisor-de-fluxo.md +60 -0
  121. package/squads/07-ux-experiencia/agentes/revisor-de-ux.md +62 -0
  122. package/squads/07-ux-experiencia/agentes/ux-writer.md +62 -0
  123. package/squads/07-ux-experiencia/squad.md +68 -0
  124. package/squads/08-growth/agentes/analista-de-alavancas.md +63 -0
  125. package/squads/08-growth/agentes/analista-de-gargalos.md +63 -0
  126. package/squads/08-growth/agentes/especialista-em-conversao.md +65 -0
  127. package/squads/08-growth/agentes/estrategista-de-growth.md +65 -0
  128. package/squads/08-growth/agentes/integrador-de-funil.md +64 -0
  129. package/squads/08-growth/agentes/leitor-de-performance-de-crescimento.md +63 -0
  130. package/squads/08-growth/agentes/planejador-de-testes.md +63 -0
  131. package/squads/08-growth/agentes/priorizador-de-experimentos.md +63 -0
  132. package/squads/08-growth/agentes/revisor-de-growth.md +64 -0
  133. package/squads/08-growth/agentes/revisor-de-hipoteses.md +64 -0
  134. package/squads/08-growth/squad.md +69 -0
  135. package/squads/09-seo/agentes/analista-de-intencao-de-busca.md +60 -0
  136. package/squads/09-seo/agentes/auditor-de-seo.md +60 -0
  137. package/squads/09-seo/agentes/criador-de-brief-seo.md +61 -0
  138. package/squads/09-seo/agentes/estrategista-de-seo.md +61 -0
  139. package/squads/09-seo/agentes/organizador-de-arquitetura-tematica.md +61 -0
  140. package/squads/09-seo/agentes/pesquisador-de-palavras-chave.md +60 -0
  141. package/squads/09-seo/agentes/planejador-de-clusters.md +61 -0
  142. package/squads/09-seo/agentes/planejador-de-links-internos.md +60 -0
  143. package/squads/09-seo/agentes/revisor-de-seo.md +61 -0
  144. package/squads/09-seo/agentes/revisor-de-serp-fit.md +60 -0
  145. package/squads/09-seo/squad.md +67 -0
  146. package/squads/10-comercial/agentes/arquiteto-de-narrativa-comercial.md +65 -0
  147. package/squads/10-comercial/agentes/copywriter-de-follow-up-comercial.md +63 -0
  148. package/squads/10-comercial/agentes/criador-de-soundbites-comerciais.md +62 -0
  149. package/squads/10-comercial/agentes/estrategista-comercial.md +65 -0
  150. package/squads/10-comercial/agentes/estruturador-de-proposta.md +63 -0
  151. package/squads/10-comercial/agentes/organizador-de-materiais-comerciais.md +62 -0
  152. package/squads/10-comercial/agentes/redator-de-pitch.md +63 -0
  153. package/squads/10-comercial/agentes/revisor-comercial.md +65 -0
  154. package/squads/10-comercial/agentes/revisor-de-objecoes.md +63 -0
  155. package/squads/10-comercial/agentes/tradutor-de-posicionamento-para-vendas.md +62 -0
  156. package/squads/10-comercial/squad.md +70 -0
  157. package/squads/11-oferta-monetizacao/agentes/analista-de-logica-de-monetizacao.md +60 -0
  158. package/squads/11-oferta-monetizacao/agentes/arquiteto-de-oferta.md +62 -0
  159. package/squads/11-oferta-monetizacao/agentes/designer-de-bonus.md +60 -0
  160. package/squads/11-oferta-monetizacao/agentes/especialista-em-garantia.md +61 -0
  161. package/squads/11-oferta-monetizacao/agentes/estrategista-de-monetizacao.md +62 -0
  162. package/squads/11-oferta-monetizacao/agentes/estruturador-de-pacotes.md +61 -0
  163. package/squads/11-oferta-monetizacao/agentes/planejador-de-escada-de-valor.md +61 -0
  164. package/squads/11-oferta-monetizacao/agentes/planejador-de-progressao.md +60 -0
  165. package/squads/11-oferta-monetizacao/agentes/revisor-de-oferta.md +61 -0
  166. package/squads/11-oferta-monetizacao/agentes/revisor-de-stack-de-valor.md +60 -0
  167. package/squads/11-oferta-monetizacao/squad.md +69 -0
  168. package/squads/12-operacoes/agentes/arquiteto-de-operacoes.md +61 -0
  169. package/squads/12-operacoes/agentes/auditor-de-processo.md +60 -0
  170. package/squads/12-operacoes/agentes/criador-de-checklist.md +60 -0
  171. package/squads/12-operacoes/agentes/documentador-operacional.md +62 -0
  172. package/squads/12-operacoes/agentes/estruturador-de-passagens-entre-squads.md +61 -0
  173. package/squads/12-operacoes/agentes/mapeador-de-fluxos.md +60 -0
  174. package/squads/12-operacoes/agentes/organizador-de-playbooks.md +60 -0
  175. package/squads/12-operacoes/agentes/redator-de-sop.md +61 -0
  176. package/squads/12-operacoes/agentes/revisor-operacional.md +64 -0
  177. package/squads/12-operacoes/agentes/verificador-de-governanca.md +61 -0
  178. package/squads/12-operacoes/squad.md +64 -0
  179. package/squads/13-qualidade/agentes/auditor-de-consistencia.md +62 -0
  180. package/squads/13-qualidade/agentes/auditor-de-friccao.md +62 -0
  181. package/squads/13-qualidade/agentes/critico-de-conversao.md +65 -0
  182. package/squads/13-qualidade/agentes/guardiao-de-aprovacao-final.md +62 -0
  183. package/squads/13-qualidade/agentes/identificador-de-risco-de-entrega.md +62 -0
  184. package/squads/13-qualidade/agentes/lider-de-qualidade.md +62 -0
  185. package/squads/13-qualidade/agentes/revisor-de-clareza.md +61 -0
  186. package/squads/13-qualidade/agentes/revisor-de-coerencia-de-mensagem.md +62 -0
  187. package/squads/13-qualidade/agentes/revisor-de-prontidao.md +62 -0
  188. package/squads/13-qualidade/agentes/verificador-de-logica.md +60 -0
  189. package/squads/13-qualidade/squad.md +63 -0
  190. package/squads/14-automacao-sistemas/agentes/arquiteto-de-sistema.md +61 -0
  191. package/squads/14-automacao-sistemas/agentes/auditor-de-coerencia-sistemica.md +62 -0
  192. package/squads/14-automacao-sistemas/agentes/curador-de-matrizes.md +60 -0
  193. package/squads/14-automacao-sistemas/agentes/designer-de-orquestracao.md +61 -0
  194. package/squads/14-automacao-sistemas/agentes/estruturador-de-skills.md +61 -0
  195. package/squads/14-automacao-sistemas/agentes/gestor-de-estado.md +61 -0
  196. package/squads/14-automacao-sistemas/agentes/guardiao-da-estrutura-do-framework.md +62 -0
  197. package/squads/14-automacao-sistemas/agentes/integrador-de-fluxos.md +61 -0
  198. package/squads/14-automacao-sistemas/agentes/planejador-de-automacao.md +61 -0
  199. package/squads/14-automacao-sistemas/agentes/revisor-de-sistema.md +62 -0
  200. package/squads/14-automacao-sistemas/squad.md +66 -0
  201. package/squads/15-executivo-pmo/agentes/coordenador-de-pmo.md +61 -0
  202. package/squads/15-executivo-pmo/agentes/definidor-de-escopo.md +62 -0
  203. package/squads/15-executivo-pmo/agentes/estrategista-executivo.md +62 -0
  204. package/squads/15-executivo-pmo/agentes/guardiao-de-escopo.md +61 -0
  205. package/squads/15-executivo-pmo/agentes/organizador-de-ativacao-de-squads.md +61 -0
  206. package/squads/15-executivo-pmo/agentes/planejador-de-prioridades.md +61 -0
  207. package/squads/15-executivo-pmo/agentes/registrador-de-decisoes.md +61 -0
  208. package/squads/15-executivo-pmo/agentes/revisor-de-dependencias.md +60 -0
  209. package/squads/15-executivo-pmo/agentes/revisor-executivo.md +63 -0
  210. package/squads/15-executivo-pmo/agentes/sequenciador-de-execucao.md +60 -0
  211. package/squads/15-executivo-pmo/squad.md +66 -0
  212. package/squads/16-trafego-pago/agentes/analista-de-metricas-e-otimizacao.md +62 -0
  213. package/squads/16-trafego-pago/agentes/analista-de-publicos.md +61 -0
  214. package/squads/16-trafego-pago/agentes/auditor-de-contas-e-campanhas.md +60 -0
  215. package/squads/16-trafego-pago/agentes/escalador-de-campanhas.md +61 -0
  216. package/squads/16-trafego-pago/agentes/especialista-em-criativos-de-performance.md +61 -0
  217. package/squads/16-trafego-pago/agentes/especialista-em-estrutura-de-funil-pago.md +63 -0
  218. package/squads/16-trafego-pago/agentes/estrategista-de-trafego-pago.md +67 -0
  219. package/squads/16-trafego-pago/agentes/gestor-de-remarketing.md +63 -0
  220. package/squads/16-trafego-pago/agentes/planejador-de-campanhas.md +61 -0
  221. package/squads/16-trafego-pago/agentes/revisor-de-performance.md +62 -0
  222. package/squads/16-trafego-pago/skills/calcular-faixa-de-investimento.md +61 -0
  223. package/squads/16-trafego-pago/squad.md +73 -0
@@ -0,0 +1,356 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Fase 5/6 do /edusquads investigar.
4
+
5
+ Conclui uma investigação a partir do run_id:
6
+ - preenche síntese interpretativa com filtros por plataforma
7
+ - preenche extrações úteis por squad
8
+ - calcula score de confiança dos insights
9
+ - aproveita coleta estruturada (quando disponível)
10
+ - registra limites da evidência
11
+ - atualiza status do run para concluído
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import argparse
17
+ import collections
18
+ import json
19
+ import re
20
+ from pathlib import Path
21
+
22
+ ROOT = Path(__file__).resolve().parents[2]
23
+ EVID_DIR = ROOT / "_edusquads" / "evidencias"
24
+ RUNS_DIR = ROOT / "_edusquads" / "runs"
25
+ RUNS_INDEX = RUNS_DIR / "RUNS-INDEX.md"
26
+
27
+ BASE_STOPWORDS = {
28
+ "para", "com", "uma", "como", "mais", "sobre", "esta", "esse", "isso", "pela", "pelo",
29
+ "from", "meta", "instagram", "https", "www", "login", "account", "your", "you", "and", "the",
30
+ "that", "this", "from", "have", "will", "where", "which", "quando", "depois", "entre", "tambem",
31
+ "password", "email", "mobile", "create", "forgot", "terms", "privacy", "jobs", "help", "blog",
32
+ }
33
+
34
+ PLATFORM_NOISE = {
35
+ "instagram": {"threads", "verified", "accounts", "facebook", "meta", "moments"},
36
+ "youtube": {"shorts", "subscribe", "watch", "playlist", "channel"},
37
+ "linkedin": {"connect", "message", "premium", "hiring", "follow"},
38
+ "x-twitter": {"retweet", "reply", "quote", "tweet", "tweets"},
39
+ }
40
+
41
+ CTA_RE = re.compile(r"\b(comente|clique|saiba|inscreva|assine|dm|direct|link|cadastre|baixe|seguir|salvar|compartilhar|agende|mensagem)\b", re.I)
42
+ FORMAT_RE = re.compile(r"\b(carrossel|reel|reels|video|vídeo|thread|post|posts|live|stories|newsletter|artigo|webinar|aula|corte)\b", re.I)
43
+
44
+
45
+ def clamp(v: int, lo: int = 0, hi: int = 100) -> int:
46
+ return max(lo, min(hi, v))
47
+
48
+
49
+ def find_evidence_file(run_id: str) -> Path:
50
+ matches = sorted(EVID_DIR.glob(f"EVIDENCIA-{run_id}-*.md"))
51
+ if not matches:
52
+ raise SystemExit(f"Nenhuma evidência encontrada para {run_id}")
53
+ return matches[0]
54
+
55
+
56
+ def find_run_file(run_id: str) -> Path:
57
+ p = RUNS_DIR / f"{run_id}.md"
58
+ if not p.exists():
59
+ raise SystemExit(f"Run não encontrado: {p}")
60
+ return p
61
+
62
+
63
+ def extract_field(pattern: str, text: str, fallback: str = "") -> str:
64
+ m = re.search(pattern, text)
65
+ return m.group(1).strip() if m else fallback
66
+
67
+
68
+ def extract_top_terms(text: str, platform: str, n: int = 8) -> list[str]:
69
+ words = re.findall(r"[A-Za-zÀ-ÿ]{4,}", text.lower())
70
+ noise = PLATFORM_NOISE.get(platform, set())
71
+ words = [w for w in words if w not in BASE_STOPWORDS and w not in noise]
72
+ counter = collections.Counter(words)
73
+ return [w for w, _ in counter.most_common(n)]
74
+
75
+
76
+ def extract_matches(pattern: re.Pattern[str], text: str, limit: int = 8) -> list[str]:
77
+ found = [m.group(1).lower() for m in pattern.finditer(text)]
78
+ uniq: list[str] = []
79
+ for x in found:
80
+ if x not in uniq:
81
+ uniq.append(x)
82
+ return uniq[:limit]
83
+
84
+
85
+ def load_structured_if_exists(run_id: str) -> dict | None:
86
+ p = EVID_DIR / f"ESTRUTURADA-{run_id}.json"
87
+ if not p.exists():
88
+ return None
89
+ try:
90
+ return json.loads(p.read_text(encoding="utf-8"))
91
+ except Exception:
92
+ return None
93
+
94
+
95
+ def compute_confidence(
96
+ source_text: str,
97
+ terms: list[str],
98
+ formats: list[str],
99
+ ctas: list[str],
100
+ limitations: str,
101
+ structured: dict | None,
102
+ ) -> dict[str, int]:
103
+ chars = len(source_text)
104
+ score_base = 20
105
+
106
+ if chars >= 3000:
107
+ score_base += 30
108
+ elif chars >= 1500:
109
+ score_base += 20
110
+ elif chars >= 800:
111
+ score_base += 10
112
+ else:
113
+ score_base += 2
114
+
115
+ score_base += min(len(terms) * 4, 20)
116
+ score_base += min(len(formats) * 6, 18)
117
+ score_base += min(len(ctas) * 6, 18)
118
+
119
+ low_access_markers = ["login", "sem autenticação", "acesso parcial", "bloqueado", "exigiu", "http 999", "999"]
120
+ if any(m in limitations.lower() for m in low_access_markers):
121
+ score_base -= 20
122
+
123
+ # Bônus da coleta estruturada (Fase 6)
124
+ if structured:
125
+ completeness = int(structured.get("quality", {}).get("completeness_score", 0))
126
+ score_base += min(completeness // 8, 10)
127
+
128
+ narrativa = clamp(score_base)
129
+ formato = clamp(score_base - (10 if not formats else 0))
130
+ cta = clamp(score_base - (12 if not ctas else 0))
131
+ geral = clamp(round((narrativa + formato + cta) / 3))
132
+
133
+ return {
134
+ "geral": geral,
135
+ "narrativa": narrativa,
136
+ "formato": formato,
137
+ "cta": cta,
138
+ }
139
+
140
+
141
+ def merge_with_structured(
142
+ terms: list[str],
143
+ formats: list[str],
144
+ ctas: list[str],
145
+ structured: dict | None,
146
+ ) -> tuple[list[str], list[str], list[str], int]:
147
+ completeness = 0
148
+ if not structured:
149
+ return terms, formats, ctas, completeness
150
+
151
+ s = structured.get("signals", {})
152
+ s_terms = [str(x).lower() for x in s.get("topics", [])]
153
+ s_formats = [str(x).lower() for x in s.get("formats", [])]
154
+ s_ctas = [str(x).lower() for x in s.get("ctas", [])]
155
+ completeness = int(structured.get("quality", {}).get("completeness_score", 0))
156
+
157
+ def merge(a: list[str], b: list[str], limit: int = 10) -> list[str]:
158
+ out = list(a)
159
+ for item in b:
160
+ if item not in out:
161
+ out.append(item)
162
+ if len(out) >= limit:
163
+ break
164
+ return out
165
+
166
+ return merge(terms, s_terms), merge(formats, s_formats), merge(ctas, s_ctas), completeness
167
+
168
+
169
+ def build_synthesis(
170
+ run_id: str,
171
+ platform: str,
172
+ source_text: str,
173
+ target: str,
174
+ limitations: str,
175
+ ) -> tuple[list[str], dict[str, list[str]], dict[str, int], int]:
176
+ terms = extract_top_terms(source_text, platform=platform)
177
+ ctas = extract_matches(CTA_RE, source_text)
178
+ formats = extract_matches(FORMAT_RE, source_text)
179
+
180
+ structured = load_structured_if_exists(run_id)
181
+ terms, formats, ctas, completeness = merge_with_structured(terms, formats, ctas, structured)
182
+
183
+ conf = compute_confidence(source_text, terms, formats, ctas, limitations, structured)
184
+
185
+ synthesis = [
186
+ f"Coleta processada para `{target}` com filtro de ruído para {platform}.",
187
+ f"Termos recorrentes úteis: {', '.join(terms) if terms else 'não identificado (fonte limitada)'}.",
188
+ f"Formatos detectados: {', '.join(formats) if formats else 'não detectado com confiabilidade'}.",
189
+ f"Sinais de CTA detectados: {', '.join(ctas) if ctas else 'não detectado com confiabilidade'}.",
190
+ f"Confiabilidade geral estimada: {conf['geral']}/100.",
191
+ ]
192
+ if structured:
193
+ synthesis.append(f"Coleta estruturada aplicada com completude {completeness}/100.")
194
+
195
+ squads = {
196
+ "estrategia": [
197
+ f"Mapear narrativa central usando termos: {', '.join(terms[:4]) if terms else 'dados insuficientes'}.",
198
+ "Confrontar sinais observados com a tese/posicionamento atual da marca.",
199
+ ],
200
+ "conteudo": [
201
+ f"Priorizar formatos observados: {', '.join(formats[:4]) if formats else 'testar carrossel/reel/post por hipótese'}.",
202
+ "Montar backlog de 3 temas derivados dos termos recorrentes.",
203
+ ],
204
+ "copy": [
205
+ f"Testar variações de CTA com base em: {', '.join(ctas[:4]) if ctas else 'comente/salve/compartilhe'}.",
206
+ "Refinar hooks para promessa específica + ganho imediato.",
207
+ ],
208
+ "design": [
209
+ "Traduzir formato dominante em padrão visual consistente por canal.",
210
+ "Aplicar contraste/hierarquia para leitura rápida no feed.",
211
+ ],
212
+ }
213
+ return synthesis, squads, conf, completeness
214
+
215
+
216
+ def update_evidence(
217
+ evidence_path: Path,
218
+ synthesis: list[str],
219
+ squads: dict[str, list[str]],
220
+ source_ref: str,
221
+ limitations: str,
222
+ confidence: dict[str, int],
223
+ completeness: int,
224
+ ) -> None:
225
+ txt = evidence_path.read_text(encoding="utf-8")
226
+
227
+ txt = txt.replace(
228
+ "- (preencher após investigação de navegação)",
229
+ "\n".join(f"- {line}" for line in synthesis),
230
+ )
231
+
232
+ extractions_block = (
233
+ "## Extrações úteis para squads\n"
234
+ "- Squad de Estratégia:\n"
235
+ + "\n".join(f" - {b}" for b in squads["estrategia"]) + "\n"
236
+ + "- Squad de Conteúdo:\n"
237
+ + "\n".join(f" - {b}" for b in squads["conteudo"]) + "\n"
238
+ + "- Squad de Copy:\n"
239
+ + "\n".join(f" - {b}" for b in squads["copy"]) + "\n"
240
+ + "- Squad de Design:\n"
241
+ + "\n".join(f" - {b}" for b in squads["design"]) + "\n\n"
242
+ + "## Confiabilidade dos insights\n"
243
+ + f"- score_geral: {confidence['geral']}/100\n"
244
+ + f"- score_narrativa: {confidence['narrativa']}/100\n"
245
+ + f"- score_formato: {confidence['formato']}/100\n"
246
+ + f"- score_cta: {confidence['cta']}/100\n"
247
+ + f"- score_completude_coleta_estruturada: {completeness}/100\n\n"
248
+ + "## Limites da evidência\n"
249
+ + f"- {limitations}\n"
250
+ + f"- Fonte usada na síntese: {source_ref}\n"
251
+ )
252
+
253
+ txt = re.sub(
254
+ r"## Extrações úteis para squads\n(?:.|\n)*?## Segurança\n",
255
+ extractions_block + "\n## Segurança\n",
256
+ txt,
257
+ flags=re.M,
258
+ )
259
+
260
+ evidence_path.write_text(txt, encoding="utf-8")
261
+
262
+
263
+ def update_run(run_path: Path, source_ref: str, confidence: dict[str, int], completeness: int) -> None:
264
+ txt = run_path.read_text(encoding="utf-8")
265
+ txt = txt.replace("- status: em-andamento", "- status: concluído")
266
+
267
+ txt = txt.replace(
268
+ "### Checkpoint de produção\n- resultado: em-andamento\n- observações: investigação web pendente de execução completa.",
269
+ "### Checkpoint de produção\n- resultado: aprovado\n- observações: investigação executada e evidência preenchida automaticamente.",
270
+ )
271
+
272
+ txt = re.sub(
273
+ r"### Checkpoint de qualidade\n- resultado: .*\n- observações: .*",
274
+ "### Checkpoint de qualidade\n- resultado: aprovado\n"
275
+ f"- observações: síntese com score de confiança {confidence['geral']}/100 e completude estruturada {completeness}/100.",
276
+ txt,
277
+ )
278
+
279
+ txt = txt.replace(
280
+ "- executar navegação e preencher síntese interpretativa da evidência.\n- atualizar status para concluído ao finalizar a investigação.",
281
+ "- none.",
282
+ )
283
+
284
+ marker = "- validações realizadas:\n"
285
+ if marker in txt and source_ref not in txt:
286
+ txt = txt.replace(marker, marker + f" - síntese automática concluída com base em `{source_ref}`\n")
287
+
288
+ txt = txt.replace(str(ROOT).replace('\\', '/').rstrip('/'), "")
289
+ txt = txt.replace(str(ROOT).replace('/', '\\').rstrip('\\'), "")
290
+ txt = txt.replace("`/_edusquads/", "`_edusquads/")
291
+
292
+ run_path.write_text(txt, encoding="utf-8")
293
+
294
+
295
+ def update_runs_index_status(run_id: str, new_status: str = "concluído") -> None:
296
+ if not RUNS_INDEX.exists():
297
+ return
298
+ txt = RUNS_INDEX.read_text(encoding="utf-8")
299
+ pattern = rf"(\|\s*{re.escape(run_id)}\s*\|[^\n]*\|\s*)(planejado|em-andamento|bloqueado|concluído)(\s*\|[^\n]*\n)"
300
+ txt = re.sub(pattern, rf"\1{new_status}\3", txt)
301
+ RUNS_INDEX.write_text(txt, encoding="utf-8")
302
+
303
+
304
+ def main() -> int:
305
+ parser = argparse.ArgumentParser(description="Concluir investigação do /edusquads")
306
+ parser.add_argument("run_id", help="ID do run, ex: RUN-2026-04-11-005")
307
+ parser.add_argument("--fonte", required=True, help="Arquivo markdown fonte da coleta")
308
+ parser.add_argument("--limites", default="Coleta parcial ou condicionada por acesso/login da plataforma.")
309
+ args = parser.parse_args()
310
+
311
+ run_id = args.run_id.strip()
312
+ run_path = find_run_file(run_id)
313
+ evidence_path = find_evidence_file(run_id)
314
+ source_path = Path(args.fonte)
315
+ if not source_path.is_absolute():
316
+ source_path = (ROOT / source_path).resolve()
317
+ if not source_path.exists():
318
+ raise SystemExit(f"Fonte não encontrada: {source_path}")
319
+
320
+ source_text = source_path.read_text(encoding="utf-8", errors="ignore")
321
+ ev_text = evidence_path.read_text(encoding="utf-8")
322
+ target = extract_field(r"- url/perfil/query: (.+)", ev_text, "alvo não identificado")
323
+ platform = extract_field(r"- plataforma detectada: (.+)", ev_text, "instagram").lower()
324
+
325
+ synthesis, squads, confidence, completeness = build_synthesis(
326
+ run_id=run_id,
327
+ platform=platform,
328
+ source_text=source_text,
329
+ target=target,
330
+ limitations=args.limites,
331
+ )
332
+ source_ref = source_path.relative_to(ROOT).as_posix()
333
+
334
+ update_evidence(
335
+ evidence_path=evidence_path,
336
+ synthesis=synthesis,
337
+ squads=squads,
338
+ source_ref=source_ref,
339
+ limitations=args.limites,
340
+ confidence=confidence,
341
+ completeness=completeness,
342
+ )
343
+ update_run(run_path=run_path, source_ref=source_ref, confidence=confidence, completeness=completeness)
344
+ update_runs_index_status(run_id=run_id, new_status="concluído")
345
+
346
+ print(f"run_id={run_id}")
347
+ print(f"run_file={run_path.relative_to(ROOT).as_posix()}")
348
+ print(f"evidence_file={evidence_path.relative_to(ROOT).as_posix()}")
349
+ print(f"source_file={source_ref}")
350
+ print(f"confidence={confidence['geral']}/100")
351
+ print(f"structured_completeness={completeness}/100")
352
+ return 0
353
+
354
+
355
+ if __name__ == "__main__":
356
+ raise SystemExit(main())
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Fase 6 do /edusquads investigar.
4
+
5
+ Estrutura a coleta bruta em um artefato padronizado por plataforma:
6
+ - gera JSON estruturado por run
7
+ - gera resumo markdown para consumo humano
8
+ - calcula score de completude da coleta
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ import collections
15
+ import json
16
+ import re
17
+ from pathlib import Path
18
+
19
+ ROOT = Path(__file__).resolve().parents[2]
20
+ EVID_DIR = ROOT / "_edusquads" / "evidencias"
21
+ RUNS_DIR = ROOT / "_edusquads" / "runs"
22
+
23
+ STOPWORDS = {
24
+ "para", "com", "uma", "como", "mais", "sobre", "esta", "esse", "isso", "pela", "pelo",
25
+ "from", "meta", "instagram", "https", "www", "login", "account", "your", "you", "and", "the",
26
+ "that", "this", "from", "have", "will", "where", "which", "quando", "depois", "entre", "tambem",
27
+ "password", "email", "mobile", "create", "forgot", "terms", "privacy", "jobs", "help", "blog",
28
+ }
29
+
30
+ FORMAT_RE = re.compile(r"\b(carrossel|reel|reels|video|vídeo|thread|post|posts|live|stories|newsletter|artigo|webinar|aula|corte)\b", re.I)
31
+ CTA_RE = re.compile(r"\b(comente|clique|saiba|inscreva|assine|dm|direct|link|cadastre|baixe|seguir|salvar|compartilhar|agende|mensagem|comprar|fale)\b", re.I)
32
+ HASHTAG_RE = re.compile(r"#([A-Za-z0-9_]{2,})")
33
+ URL_RE = re.compile(r"https?://[^\s)]+")
34
+
35
+
36
+ def clamp(v: int, lo: int = 0, hi: int = 100) -> int:
37
+ return max(lo, min(hi, v))
38
+
39
+
40
+ def find_run(run_id: str) -> Path:
41
+ p = RUNS_DIR / f"{run_id}.md"
42
+ if not p.exists():
43
+ raise SystemExit(f"Run não encontrado: {p}")
44
+ return p
45
+
46
+
47
+ def find_evidence(run_id: str) -> Path:
48
+ matches = sorted(EVID_DIR.glob(f"EVIDENCIA-{run_id}-*.md"))
49
+ if not matches:
50
+ raise SystemExit(f"Evidência não encontrada para {run_id}")
51
+ return matches[0]
52
+
53
+
54
+ def detect_platform_from_evidence(evidence_text: str) -> str:
55
+ m = re.search(r"- plataforma detectada: (.+)", evidence_text)
56
+ return m.group(1).strip().lower() if m else "instagram"
57
+
58
+
59
+ def extract_target(evidence_text: str) -> str:
60
+ m = re.search(r"- url/perfil/query: (.+)", evidence_text)
61
+ return m.group(1).strip() if m else "não identificado"
62
+
63
+
64
+ def uniq(items: list[str], limit: int = 20) -> list[str]:
65
+ out: list[str] = []
66
+ for i in items:
67
+ s = i.strip()
68
+ if s and s not in out:
69
+ out.append(s)
70
+ if len(out) >= limit:
71
+ break
72
+ return out
73
+
74
+
75
+ def extract_top_terms(text: str, limit: int = 12) -> list[str]:
76
+ words = re.findall(r"[A-Za-zÀ-ÿ]{4,}", text.lower())
77
+ words = [w for w in words if w not in STOPWORDS]
78
+ counter = collections.Counter(words)
79
+ return [w for w, _ in counter.most_common(limit)]
80
+
81
+
82
+ def platform_fields(platform: str, text: str) -> dict:
83
+ t = text.lower()
84
+
85
+ if platform == "instagram":
86
+ return {
87
+ "bio_detectada": bool(re.search(r"\bbio\b|perfil|creator|digital|marketing", t)),
88
+ "destaques_detectados": bool(re.search(r"destaques|highlights", t)),
89
+ "sinal_reels": bool(re.search(r"\breels?\b", t)),
90
+ "sinal_carrossel": bool(re.search(r"\bcarrossel|slides?\b", t)),
91
+ }
92
+
93
+ if platform == "youtube":
94
+ return {
95
+ "sinal_titulo_video": bool(re.search(r"\bwatch\b|\bvídeo\b|\bvideo\b", t)),
96
+ "sinal_thumb": bool(re.search(r"\bthumb|thumbnail\b", t)),
97
+ "sinal_playlist": bool(re.search(r"\bplaylist\b", t)),
98
+ "sinal_descricao": bool(re.search(r"\bdescrição|description\b", t)),
99
+ }
100
+
101
+ if platform == "linkedin":
102
+ return {
103
+ "sinal_headline": bool(re.search(r"\bheadline\b|\btitle\b|\bcargo\b", t)),
104
+ "sinal_about": bool(re.search(r"\babout\b|\bsobre\b", t)),
105
+ "sinal_post": bool(re.search(r"\bpost|publicação|publicacao\b", t)),
106
+ "sinal_prova_social": bool(re.search(r"\bcomentários|comentarios|recomendações|recomendacoes\b", t)),
107
+ }
108
+
109
+ if platform == "x-twitter":
110
+ return {
111
+ "sinal_thread": bool(re.search(r"\bthread\b", t)),
112
+ "sinal_reply": bool(re.search(r"\breply|resposta\b", t)),
113
+ "sinal_retweet": bool(re.search(r"\bretweet\b", t)),
114
+ "sinal_link_externo": bool(re.search(r"http", t)),
115
+ }
116
+
117
+ return {"sinal_generico": True}
118
+
119
+
120
+ def completeness_score(chars: int, formats: list[str], ctas: list[str], links: list[str], pf: dict) -> int:
121
+ score = 20
122
+ if chars > 2500:
123
+ score += 30
124
+ elif chars > 1200:
125
+ score += 20
126
+ elif chars > 600:
127
+ score += 10
128
+
129
+ score += min(len(formats) * 8, 20)
130
+ score += min(len(ctas) * 8, 20)
131
+ score += min(len(links) * 3, 10)
132
+ score += sum(1 for v in pf.values() if v) * 3
133
+ return clamp(score)
134
+
135
+
136
+ def write_outputs(run_id: str, data: dict) -> tuple[Path, Path]:
137
+ json_path = EVID_DIR / f"ESTRUTURADA-{run_id}.json"
138
+ md_path = EVID_DIR / f"ESTRUTURADA-{run_id}.md"
139
+
140
+ json_path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
141
+
142
+ md = [
143
+ f"# Coleta Estruturada — {run_id}",
144
+ "",
145
+ "## Metadados",
146
+ f"- plataforma: {data['platform']}",
147
+ f"- alvo: {data['target']}",
148
+ f"- caracteres_fonte: {data['source']['chars']}",
149
+ f"- linhas_fonte: {data['source']['lines']}",
150
+ "",
151
+ "## Sinais extraídos",
152
+ f"- formatos: {', '.join(data['signals']['formats']) if data['signals']['formats'] else 'nenhum'}",
153
+ f"- ctas: {', '.join(data['signals']['ctas']) if data['signals']['ctas'] else 'nenhum'}",
154
+ f"- hashtags: {', '.join(data['signals']['hashtags']) if data['signals']['hashtags'] else 'nenhuma'}",
155
+ f"- links_detectados: {len(data['signals']['links'])}",
156
+ f"- topicos: {', '.join(data['signals']['topics']) if data['signals']['topics'] else 'nenhum'}",
157
+ "",
158
+ "## Campos por plataforma",
159
+ ]
160
+
161
+ for k, v in data["platform_fields"].items():
162
+ md.append(f"- {k}: {'sim' if v else 'não'}")
163
+
164
+ md += [
165
+ "",
166
+ "## Qualidade da coleta",
167
+ f"- score_completude: {data['quality']['completeness_score']}/100",
168
+ ]
169
+
170
+ md_path.write_text("\n".join(md) + "\n", encoding="utf-8")
171
+ return json_path, md_path
172
+
173
+
174
+ def main() -> int:
175
+ parser = argparse.ArgumentParser(description="Estruturar coleta por plataforma")
176
+ parser.add_argument("run_id", help="ID do run (ex.: RUN-2026-04-11-005)")
177
+ parser.add_argument("--fonte", required=True, help="Arquivo markdown da coleta bruta")
178
+ args = parser.parse_args()
179
+
180
+ run_id = args.run_id.strip()
181
+ find_run(run_id)
182
+
183
+ evidence_path = find_evidence(run_id)
184
+ evidence_text = evidence_path.read_text(encoding="utf-8")
185
+
186
+ source_path = Path(args.fonte)
187
+ if not source_path.is_absolute():
188
+ source_path = (ROOT / source_path).resolve()
189
+ if not source_path.exists():
190
+ raise SystemExit(f"Fonte não encontrada: {source_path}")
191
+
192
+ source_text = source_path.read_text(encoding="utf-8", errors="ignore")
193
+
194
+ platform = detect_platform_from_evidence(evidence_text)
195
+ target = extract_target(evidence_text)
196
+
197
+ formats = uniq([m.group(1).lower() for m in FORMAT_RE.finditer(source_text)], limit=12)
198
+ ctas = uniq([m.group(1).lower() for m in CTA_RE.finditer(source_text)], limit=12)
199
+ hashtags = uniq([m.group(1).lower() for m in HASHTAG_RE.finditer(source_text)], limit=30)
200
+ links = uniq(URL_RE.findall(source_text), limit=100)
201
+ topics = extract_top_terms(source_text, limit=12)
202
+ pf = platform_fields(platform, source_text)
203
+
204
+ data = {
205
+ "run_id": run_id,
206
+ "platform": platform,
207
+ "target": target,
208
+ "source": {
209
+ "path": source_path.relative_to(ROOT).as_posix(),
210
+ "chars": len(source_text),
211
+ "lines": len(source_text.splitlines()),
212
+ },
213
+ "signals": {
214
+ "formats": formats,
215
+ "ctas": ctas,
216
+ "hashtags": hashtags,
217
+ "links": links,
218
+ "topics": topics,
219
+ },
220
+ "platform_fields": pf,
221
+ "quality": {
222
+ "completeness_score": completeness_score(len(source_text), formats, ctas, links, pf)
223
+ },
224
+ }
225
+
226
+ json_path, md_path = write_outputs(run_id, data)
227
+
228
+ print(f"run_id={run_id}")
229
+ print(f"platform={platform}")
230
+ print(f"structured_json={json_path.relative_to(ROOT).as_posix()}")
231
+ print(f"structured_md={md_path.relative_to(ROOT).as_posix()}")
232
+ print(f"completeness={data['quality']['completeness_score']}/100")
233
+ return 0
234
+
235
+
236
+ if __name__ == "__main__":
237
+ raise SystemExit(main())