raptor-aios 0.8.3 → 0.10.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 (46) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +26 -26
  3. package/dist/_core/dist/agents/context-file.js +8 -0
  4. package/dist/_core/dist/extensions/types.js +2 -0
  5. package/dist/_core/dist/jira/dialects.js +2 -2
  6. package/dist/_core/dist/jira/index.js +1 -1
  7. package/dist/_core/dist/jira/mapper.js +152 -13
  8. package/dist/_core/dist/jira/mcp-client.js +1 -0
  9. package/dist/_core/dist/presets/gol-smiles-deep.js +268 -0
  10. package/dist/_core/dist/presets/gol-smiles-gates.js +341 -0
  11. package/dist/_core/dist/presets/gol-smiles.js +77 -0
  12. package/dist/_core/dist/presets/index.js +4 -1
  13. package/dist/_core/dist/presets/registry.js +4 -0
  14. package/dist/_core/dist/presets/renderer.js +6 -0
  15. package/dist/_core/dist/verify/arch.js +82 -0
  16. package/dist/_core/dist/verify/i18n.js +182 -0
  17. package/dist/_core/dist/verify/index.js +3 -0
  18. package/dist/_core/dist/verify/tangerina.js +645 -0
  19. package/dist/_core/package.json +1 -1
  20. package/dist/_core/templates/plan.md.hbs +23 -2
  21. package/dist/_core/templates/raptor.yml.hbs +1 -1
  22. package/dist/_core/templates/spec.md.hbs +24 -3
  23. package/dist/commands/init.js +51 -18
  24. package/dist/commands/jira/pull.js +7 -1
  25. package/dist/commands/new.js +3 -0
  26. package/dist/commands/plan.js +3 -1
  27. package/dist/commands/preset/add.js +26 -1
  28. package/dist/commands/upgrade.js +13 -3
  29. package/dist/commands/verify/arch.js +116 -0
  30. package/dist/commands/verify/i18n.js +79 -0
  31. package/dist/commands/verify/tangerina.js +111 -0
  32. package/dist/shared/gol-verify.js +51 -0
  33. package/dist/shared/jira.js +18 -7
  34. package/dist/shared/scaffold.js +60 -5
  35. package/extensions/gol-smiles/extension.yml +28 -0
  36. package/extensions/gol-smiles/knowledge/app-kit-catalog.md +54 -0
  37. package/extensions/gol-smiles/knowledge/composition-recipes.md +84 -0
  38. package/extensions/gol-smiles/knowledge/conformity-checklist.md +34 -0
  39. package/extensions/gol-smiles/knowledge/figma-tangerina-map.json +927 -0
  40. package/extensions/gol-smiles/knowledge/tangerina-catalog.md +39 -0
  41. package/extensions/gol-smiles/knowledge/tokens-reference.md +32 -0
  42. package/package.json +1 -1
  43. package/scripts/generate-tangerina-design-map.mjs +147 -0
  44. package/scripts/prepare-npm.mjs +1 -1
  45. package/templates/plan-template.md +3 -0
  46. package/templates/spec-template.md +12 -0
package/CHANGELOG.md CHANGED
@@ -3,6 +3,36 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
+ ## [0.10.0] - 2026-06-11
7
+
8
+ ### Added
9
+
10
+ - **Jira → spec: nomes de campo resolvidos + filtro de relevância (`packages/core/src/jira/`).** Um card de Jira corporativo carrega centenas de custom fields de configuração (o `CDPOS-6532` real: 1408 campos, 515 não-nulos) que antes inundavam a `## Problem Statement` com blocos `### customfield_NNNNN` ilegíveis. Agora:
11
+ - **Nomes resolvidos.** O dialeto Rovo (hospedado, OAuth) passa a enviar `expand: "names"`, então cada `customfield_NNNNN` ganha seu rótulo humano ("Critérios de Aceite"). O `mcp-atlassian` (≥ 0.11.2) tem o rótulo desembrulhado do wrapper `{value, name}` (ele nunca ecoa o mapa `names`); `renderedFields` deixou de ser pedido (o servidor o descarta e a combinação suprime custom fields em algumas versões).
12
+ - **Filtro de relevância (`filterCustomFields`).** Camadas determinísticas (PT/EN, singular+plural): valor-ruído (enums, datas, placeholders) e rótulos de config (Responsável, Relator, Categorias, Branch, Repositório, EI/EO/EQ, CMDB, Nova Data…) caem; allowlist de história (Requisitos, Cenários, Critérios de Aceite, Ressalva de testes, Figma, Traduções, Documentação, Anexo…) e links de ferramenta de design (figma/miro/zeplin/notion/confluence) ficam — avaliados ANTES do denylist. Desconhecidos só ficam com prosa substantiva (rede *lose-nothing*).
13
+ - **`jira.custom_fields` ganha `exclude` e force-keep** (chaves case-insensitive): `<campo>: exclude` derruba; `<campo>: <bucket>` força a manter.
14
+ - **Anexos de imagem** (`image/*`) entram como `### Attachments (images)` (links, não baixados); outros tipos ficam no card.
15
+ - **Critérios de Aceite agrupados por cenário** (`acceptanceLines`): blocos "Cenário N …" viram um `[AC-#]` cada (não um por linha de Gherkin).
16
+
17
+ ### Changed
18
+
19
+ - **`flattenAdf` rende `taskItem` e `table`.** Uma matriz de aplicabilidade do Jira (grade de checkboxes) agora vira texto legível: **só os itens marcados `[x]` sobrevivem** (os desmarcados são ruído) e tabelas viram uma linha por row com células separadas por `|`. No `CDPOS-6532`, "Cenários aplicáveis" encolheu de 2528 → 607 chars, mostrando o escopo real.
20
+ - **URL do card browsable.** `resolveUrl` prefere o `base_url` do `raptor.yml` ao `self` do gateway `api.atlassian.com` (que não abre no navegador); `jira.base_url` é encanado do `raptor.yml` ao cliente OAuth.
21
+ - **`raptor jira pull`** imprime quantos custom fields foram filtrados (descoberta para o escape hatch); `--json` segue despejando tudo sem filtro. O `jira-refresh.md` (clarify) usa o mesmo filtro — os três consumidores (`new`, `pull`, `jira-refresh`) mostram o mesmo card.
22
+
23
+ Doc: [`docs/jira-spec-enrichment.md`](docs/jira-spec-enrichment.md) (seções F1–F4 + F3½ + §4b card corporativo). Validado E2E contra o `CDPOS-6532` real via OAuth.
24
+
25
+ ## [0.9.0] - 2026-06-11
26
+
27
+ ### Added
28
+
29
+ - **Preset `gol-smiles` (G1–G6), empilhável sobre `mobile-opinionated`.** Novo preset bundled (`packages/core/src/presets/gol-smiles.ts` + `gol-smiles-gates.ts`) focado no ecossistema Gol/Smiles (RN + Design System Tangerina, arquitetura `modernization/`): **G1** Tangerina-first UI (vocabulário fechado de componentes/receitas), **G2** arquitetura moderna (Zustand + RHF/Zod), **G3** i18n pt-BR/en/es, **G4** contrato de analytics, **G5** rollout por feature flag, **G6** paridade multi-marca — todos com opt-out honesto. Registrado no `REGISTRY` com aliases `gol`/`smiles`. `init --preset mobile-opinionated,gol-smiles` empilha os 12 artigos e 12 gates; `raptor preset add gol-smiles` ativa num projeto existente.
30
+ - **Render multi-preset na constituição (D-10).** `raptor init --preset a,b` aceita um stack comma-separated e renderiza os artigos de **todos** os presets (`renderPresetsArticles`, atribuição de fonte por preset), gravando `presets:` no `raptor.yml`; `raptor preset add` **anexa** os artigos do novo preset à constituição (append seguro e idempotente, sem sobrescrever artigos do projeto).
31
+ - **Knowledge pack via extensão de preset (D-3).** O schema `raptor.extension/v1` ganhou `provides.knowledge[]` e `requires.preset`; o scaffold instala extensões **condicionalmente ao preset ativo** (`installBundledExtensions`/`collectFrameworkFiles` filtram; `collectKnowledgeRefs` alimenta o bloco de contexto). `buildContextBlock` ganhou a seção "Knowledge packs". A extensão `extensions/gol-smiles/` traz 5 catálogos (Tangerina, kit interno com classificação A/B/C/D, receitas de composição, tokens, 19 regras de conformidade) + o dicionário Figma→Tangerina, referenciados no `CLAUDE.md`/`AGENTS.md`.
32
+ - **Frontmatter ciente do preset (D-2).** `spec.md.hbs`/`plan.md.hbs` emitem os blocos G* (`ui`/`i18n`/`analytics`/`brands` na spec; `architecture`/`rollout` no plan) **somente** quando `gol-smiles` está ativo — projetos non-gol ficam intactos. Mirrors de doc sincronizados.
33
+ - **Verificadores `raptor verify tangerina | i18n | arch` (Fase 3).** Runners reais que medem o código (`packages/core/src/verify/`): tokens literais/lista negra do kit/receita-base e receitas declaradas (tangerina, com modo `--design`), paridade de chaves pt-BR/en/es (i18n), imports banidos e aliases (arch). Achados carregam `regra`/`receita#asserção` + `arquivo:linha` + severidade (`error` bloqueia; `warning` só com `--strict`).
34
+ - **Modo deep gate→verify (D-7), opt-in.** Com `gol_smiles.deep_verify: true` no `raptor.yml`, os gates G1/G2/G3 também executam os runners na avaliação e agregam o resultado em `details` (erro determinístico no código reprova o gate). Sem o opt-in, os gates seguem puramente declarativos (padrão dos gates mobile).
35
+
6
36
  ## [0.8.3] - 2026-06-10
7
37
 
8
38
  ### Documentation
package/README.md CHANGED
@@ -360,14 +360,14 @@ Gates são checagens versionadas com um **nível**: 🔴 `critical` (bloqueia, i
360
360
 
361
361
  Cada gate exige campos no *frontmatter* de `spec.md` ou `plan.md` — declarativos, verificáveis e (para alguns) **mensuráveis** via `verify`.
362
362
 
363
- | Gate | Nível | Exige no frontmatter | Verificável por |
364
- | --------------------------------------------- | ----- | ------------------------------------------------------------ | ------------------ |
363
+ | Gate | Nível | Exige no frontmatter | Verificável por |
364
+ | --------------------------------------------- | ----- | --------------------------------------------------------------------------------------------------------------------- | ------------------ |
365
365
  | **M1 — App/Play Store Compliance** | 🟡 | `spec.stores.ios_permissions`, `.android_permissions` (ou `stores.no_restricted_apis: "<motivo>"` p/ opt-out honesto) | `verify stores` |
366
- | **M2 — Privacidade & Residência (LGPD/GDPR)** | 🟡 | `plan.privacy.lawful_basis`, `.residency`, `.retention` | — |
367
- | **M3 — Acessibilidade (WCAG AA)** | 🟡 | `spec.a11y.wcag_level`, `.criteria` | `verify a11y` |
368
- | **M4 — Matriz de SO** | 🟡 | `plan.os_matrix.ios_min`, `.android_min` | `verify os-matrix` |
369
- | **M5 — Orçamento de Performance** | 🟡 | `plan.perf_budget.ttfi_ms`, `.interactions_ms` | `verify perf` |
370
- | **M6 — Crash-Free SLO & Observabilidade** | 🔵 | `plan.crash_free_slo` (≥99.5), `plan.observability.provider` | — |
366
+ | **M2 — Privacidade & Residência (LGPD/GDPR)** | 🟡 | `plan.privacy.lawful_basis`, `.residency`, `.retention` | — |
367
+ | **M3 — Acessibilidade (WCAG AA)** | 🟡 | `spec.a11y.wcag_level`, `.criteria` | `verify a11y` |
368
+ | **M4 — Matriz de SO** | 🟡 | `plan.os_matrix.ios_min`, `.android_min` | `verify os-matrix` |
369
+ | **M5 — Orçamento de Performance** | 🟡 | `plan.perf_budget.ttfi_ms`, `.interactions_ms` | `verify perf` |
370
+ | **M6 — Crash-Free SLO & Observabilidade** | 🔵 | `plan.crash_free_slo` (≥99.5), `plan.observability.provider` | — |
371
371
 
372
372
  ### 🔗 M7 — Rastreabilidade código↔spec
373
373
 
@@ -454,17 +454,17 @@ Declarar não é cumprir. O Raptor **mede** e compara com o declarado:
454
454
 
455
455
  ## 🧰 Capacidades do Raptor
456
456
 
457
- | Capacidade | Para que serve | Comandos |
458
- | ----------------------- | --------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
459
- | 🎚️ **Presets** | Conjuntos reusáveis de artigos + gates, empilháveis. Bundled: `mobile-opinionated`, `lean`. | `raptor preset list · add · info · remove` |
457
+ | Capacidade | Para que serve | Comandos |
458
+ | ----------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
459
+ | 🎚️ **Presets** | Conjuntos reusáveis de artigos + gates, empilháveis. Bundled: `mobile-opinionated`, `lean`. | `raptor preset list · add · info · remove` |
460
460
  | 🔁 **Workflows** | Orquestram o ciclo via YAML, com gates de revisão humana. Bundled: `sdd-cycle`, `sdd-cycle-quick`. | `raptor workflow catalog · run · status · resume · list` |
461
- | 🤖 **Agentes** | Materializa os slash commands por agente. | `raptor add-agent` · `raptor list-agents` |
462
- | 🧠 **Skills** | Habilidades reusáveis materializadas para os agentes. | `raptor skill add · list · sync · remove` |
463
- | 🔌 **MCP** | Registra servidores MCP e materializa nas configs nativas dos agentes. | `raptor mcp add · list · sync · remove` |
464
- | 🪝 **Hooks** | Scripts disparados em **34 pontos** do ciclo (`before_*`/`after_*` de 17 comandos). | `raptor hook list · run` |
465
- | 📦 **Extensões** | Pacotes self-contained (templates/gates/hooks/workflows). | `raptor extension add · list · info · remove` |
466
- | 🎟️ **Jira** | Puxa issues do Jira (somente leitura) para semear specs. | `raptor jira connect · status · pull · disconnect` |
467
- | 📜 **Auditoria & trace** | Consulta a trilha de eventos e a rastreabilidade. | `raptor audit query · show` · `raptor trace` |
461
+ | 🤖 **Agentes** | Materializa os slash commands por agente. | `raptor add-agent` · `raptor list-agents` |
462
+ | 🧠 **Skills** | Habilidades reusáveis materializadas para os agentes. | `raptor skill add · list · sync · remove` |
463
+ | 🔌 **MCP** | Registra servidores MCP e materializa nas configs nativas dos agentes. | `raptor mcp add · list · sync · remove` |
464
+ | 🪝 **Hooks** | Scripts disparados em **34 pontos** do ciclo (`before_*`/`after_*` de 17 comandos). | `raptor hook list · run` |
465
+ | 📦 **Extensões** | Pacotes self-contained (templates/gates/hooks/workflows). | `raptor extension add · list · info · remove` |
466
+ | 🎟️ **Jira** | Puxa issues do Jira (somente leitura) para semear specs. | `raptor jira connect · status · pull · disconnect` |
467
+ | 📜 **Auditoria & trace** | Consulta a trilha de eventos e a rastreabilidade. | `raptor audit query · show` · `raptor trace` |
468
468
 
469
469
  ### 🎟️ Integração com Jira
470
470
 
@@ -554,15 +554,15 @@ CLAUDE.md # 📎 bloco de contexto do agente
554
554
 
555
555
  Comece pelo **[📖 Glossário Canônico](docs/glossary.md)** — a referência normativa de todos os termos, gates, hierarquias e anti-padrões do Raptor (Gate, ADR, Override, Canonical, Preset, M1–M8, C1–C5, traceability…). É a base de onboarding para usuários, mantenedores e agentes IA.
556
556
 
557
- | Documento | Sobre |
558
- | --- | --- |
559
- | **[📖 docs/glossary.md](docs/glossary.md)** | Glossário canônico: conceitos, hierarquias, catálogo de 42 gates e de comandos CLI |
560
- | [🛡️ docs/readiness-gates.md](docs/readiness-gates.md) | Gates de prontidão (`spec/plan/tasks.ready`) em detalhe |
561
- | [🎨 docs/design-gate.md](docs/design-gate.md) | Design gate, integração Figma e `assets-manifest.json` |
562
- | [🔗 docs/artifact-chain.md](docs/artifact-chain.md) | Cadeia de artefatos e rastreabilidade spec→plan→tasks→código |
563
- | [🎟️ docs/jira-spec-enrichment.md](docs/jira-spec-enrichment.md) | Enriquecimento de spec a partir de cards do Jira |
564
- | [📐 docs/templates-architecture.md](docs/templates-architecture.md) | Dualidade de templates (`.hbs` vs `*-template.md`) e Priority Stack |
565
- | [✅ docs/spec-kit-parity.md](docs/spec-kit-parity.md) | Paridade com o GitHub Spec Kit |
557
+ | Documento | Sobre |
558
+ | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
559
+ | **[📖 docs/glossary.md](docs/glossary.md)** | Glossário canônico: conceitos, hierarquias, catálogo de 42 gates e de comandos CLI |
560
+ | [🛡️ docs/readiness-gates.md](docs/readiness-gates.md) | Gates de prontidão (`spec/plan/tasks.ready`) em detalhe |
561
+ | [🎨 docs/design-gate.md](docs/design-gate.md) | Design gate, integração Figma e `assets-manifest.json` |
562
+ | [🔗 docs/artifact-chain.md](docs/artifact-chain.md) | Cadeia de artefatos e rastreabilidade spec→plan→tasks→código |
563
+ | [🎟️ docs/jira-spec-enrichment.md](docs/jira-spec-enrichment.md) | Enriquecimento de spec a partir de cards do Jira |
564
+ | [📐 docs/templates-architecture.md](docs/templates-architecture.md) | Dualidade de templates (`.hbs` vs `*-template.md`) e Priority Stack |
565
+ | [✅ docs/spec-kit-parity.md](docs/spec-kit-parity.md) | Paridade com o GitHub Spec Kit |
566
566
 
567
567
  ### Hierarquia de governança (quem manda)
568
568
 
@@ -21,6 +21,14 @@ export function buildContextBlock(opts) {
21
21
  lines.push(`\n## Applicable articles\n`);
22
22
  lines.push(`- ${opts.constitutionArticles.join(", ")}`);
23
23
  }
24
+ if (opts.knowledge && opts.knowledge.length > 0) {
25
+ lines.push(`\n## Knowledge packs\n`);
26
+ lines.push("Preset knowledge installed for this project. READ the relevant file before generating code so the output matches the house patterns (components, recipes, tokens, conventions):");
27
+ lines.push("");
28
+ for (const ref of opts.knowledge) {
29
+ lines.push(`- \`${ref}\``);
30
+ }
31
+ }
24
32
  lines.push("\n## Raptor Commands (slash commands)\n");
25
33
  lines.push("- `/raptor-new` — create a new feature spec");
26
34
  lines.push("- `/raptor-clarify` — detect and resolve [NEEDS CLARIFICATION] in spec");
@@ -26,6 +26,7 @@ export const EXTENSION_JSON_SCHEMA = {
26
26
  type: "object",
27
27
  properties: {
28
28
  raptor_version: { type: "string" },
29
+ preset: { type: "string" },
29
30
  },
30
31
  additionalProperties: false,
31
32
  },
@@ -37,6 +38,7 @@ export const EXTENSION_JSON_SCHEMA = {
37
38
  hooks: { type: "array", items: { type: "string" } },
38
39
  workflows: { type: "array", items: { type: "string" } },
39
40
  presets: { type: "array", items: { type: "string" } },
41
+ knowledge: { type: "array", items: { type: "string" } },
40
42
  },
41
43
  additionalProperties: false,
42
44
  },
@@ -12,7 +12,7 @@ export const ROVO_DIALECT = {
12
12
  transition: ["transitionJiraIssue"],
13
13
  },
14
14
  args: {
15
- getIssue: (cloudId, key) => ({ cloudId, issueIdOrKey: key }),
15
+ getIssue: (cloudId, key) => ({ cloudId, issueIdOrKey: key, expand: "names" }),
16
16
  search: (cloudId, jql, max) => ({ cloudId, jql, maxResults: max }),
17
17
  projects: (cloudId) => ({ cloudId }),
18
18
  createIssue: (input) => ({
@@ -53,7 +53,7 @@ export const MCP_ATLASSIAN_DIALECT = {
53
53
  getIssue: (_cloudId, key) => ({
54
54
  issue_key: key,
55
55
  fields: "*all",
56
- expand: "names,renderedFields",
56
+ expand: "names",
57
57
  comment_limit: 10,
58
58
  }),
59
59
  search: (_cloudId, jql, max) => ({ jql, limit: max }),
@@ -3,4 +3,4 @@ export * from "./credentials.js";
3
3
  export { AtlassianOAuthProvider, isAccessTokenExpired, openBrowser, startCallbackServer, } from "./oauth.js";
4
4
  export { connectJira, connectMcpServer, makeJiraClient, clientToToolCaller, decodeToolResult, unwrapEnvelope, expandEnv, missingEnvRefs, } from "./mcp-client.js";
5
5
  export { ROVO_DIALECT, MCP_ATLASSIAN_DIALECT, DIALECTS, resolveDialect, detectDialect, } from "./dialects.js";
6
- export { parseJiraIssue, parseCreatedIssue, extractComments, extractCustomFields, classifyField, flattenFieldValue, mapIssueToSpecContext, flattenAdf, extractAcceptanceCriteria, } from "./mapper.js";
6
+ export { parseJiraIssue, parseCreatedIssue, extractComments, extractCustomFields, extractAttachments, classifyField, filterCustomFields, isBoilerplateValue, isTemplateValue, acceptanceLines, flattenFieldValue, mapIssueToSpecContext, flattenAdf, extractAcceptanceCriteria, } from "./mapper.js";
@@ -33,18 +33,49 @@ export function parseJiraIssue(raw, baseUrl) {
33
33
  acceptanceCriteria: extractAcceptanceCriteria(fields, description),
34
34
  comments: extractComments(fields),
35
35
  customFields,
36
+ attachments: extractAttachments(fields),
36
37
  ...(url ? { url } : {}),
37
38
  };
38
39
  }
40
+ export function extractAttachments(fields) {
41
+ const list = Array.isArray(fields["attachment"])
42
+ ? fields["attachment"]
43
+ : Array.isArray(fields["attachments"])
44
+ ? fields["attachments"]
45
+ : [];
46
+ const out = [];
47
+ for (const a of list) {
48
+ if (!isRecord(a))
49
+ continue;
50
+ const filename = str(a["filename"]);
51
+ if (!filename)
52
+ continue;
53
+ out.push({
54
+ filename,
55
+ mimeType: str(a["mimeType"]) || str(a["content_type"]),
56
+ url: str(a["content"]) || str(a["url"]),
57
+ });
58
+ }
59
+ return out;
60
+ }
39
61
  export function extractCustomFields(fields, names) {
40
62
  const out = [];
41
63
  for (const [id, raw] of Object.entries(fields)) {
42
64
  if (!/^customfield_/i.test(id))
43
65
  continue;
44
- const value = flattenFieldValue(raw);
66
+ let label = str(names[id]);
67
+ let inner = raw;
68
+ if (isRecord(raw) &&
69
+ "value" in raw &&
70
+ Object.keys(raw).every((k) => k === "value" || k === "name")) {
71
+ if (!label)
72
+ label = str(raw["name"]);
73
+ inner = raw["value"];
74
+ }
75
+ const value = flattenFieldValue(inner);
45
76
  if (!value)
46
77
  continue;
47
- const name = str(names[id]) || id;
78
+ const name = label || id;
48
79
  out.push({ id, name, value, bucket: classifyField(name) });
49
80
  }
50
81
  return out;
@@ -55,21 +86,102 @@ export function classifyField(label) {
55
86
  return "acceptance";
56
87
  if (/cen[áa]rio|scenario/.test(l))
57
88
  return "scenarios";
58
- if (/n[ãa]o[-\s]?funcional|non[-\s]?functional|\brnf\b/.test(l))
89
+ if (/n[ãa]o[-\s]?funcional|non[-\s]?functional|\brnf\b|ressalva/.test(l))
59
90
  return "nonFunctional";
60
91
  if (/requisito|requirement|\brf\b/.test(l))
61
92
  return "functional";
62
93
  if (/fora do escopo|out of scope|n[ãa]o escopo/.test(l))
63
94
  return "outOfScope";
64
- if (/documenta|documentation|link|refer[êe]ncia|design|figma/.test(l))
95
+ if (/documenta|documentation|link|refer[êe]ncia|design|figma|tradu[çc]|translation/.test(l))
65
96
  return "docs";
66
97
  return "other";
67
98
  }
99
+ const ENUM_NOISE = /^(?:sim|n[ãa]o|yes|no|true|false|n\/a|na|none|nenhum|classificar|backlog|portal|no prazo|⏳|✅|❌|-+)$/i;
100
+ const PLACEHOLDER_LINE = /^(?:inserir|informar|descrever|preencher|selecionar|indicar|exportar|insira|informe|descreva|preencha|selecione|indique)\b/i;
101
+ const CARD_CONFIG_LABEL = /\b(?:respons[áa]ve(?:l|is)|relator(?:a|es|as)?|reporter|assignee|aprovador(?:a|es|as)?|squads?|categorias?|data\s+limite|nova\s+data|due\s*date|prazos?|branch(?:es)?|reposit[óo]rios?|repositor(?:y|ies)|entradas?\s+externas?|sa[íi]das?\s+externas?|consultas?\s+externas?|\bei\b|\beo\b|\beq\b|cmdb|aplica[çc][ãa]o\s+afetada|epic\s*links?|rank|development|sprints?|itera[çc](?:[ãa]o|[õo]es)|iterations?|quarters?|garantias?|story\s*points?|time\s*tracking)\b/i;
102
+ const ESSENTIAL_LABEL = /aceite|aceita[çc][ãa]o|acceptance|cen[áa]rio|scenario|requisito|requirement|\brnf\b|\brf\b|ressalva|figma|tradu[çc]|translation|documenta[çc]|documentation|refer[êe]ncia|\bdesign\b|anexo|attachment|prot[óo]tipo|prototype|hist[óo]ria|user\s*stor|fora\s+do\s+escopo|out\s+of\s+scope/i;
103
+ const DOC_TOOL_URL = /\b(?:figma\.com|miro\.com|zeplin\.(?:io|app)|notion\.so|atlassian\.net\/wiki)\b/i;
104
+ export function isBoilerplateValue(value) {
105
+ const v = value.trim();
106
+ if (!v)
107
+ return true;
108
+ if (ENUM_NOISE.test(v))
109
+ return true;
110
+ if (!v.includes("\n") && /^[\d|:.,\-T+Z\s/]+$/.test(v))
111
+ return true;
112
+ if (/^(?:espa[çc]o para inser[çc][ãa]o|campos a seguir)/i.test(v))
113
+ return true;
114
+ return false;
115
+ }
116
+ export function isTemplateValue(value) {
117
+ const v = value.trim();
118
+ if (v.length < 200)
119
+ return true;
120
+ if (v.includes("{panel") || v.includes("🚨"))
121
+ return true;
122
+ if (/^\{(?:pullrequest|dataType|json)\b/.test(v))
123
+ return true;
124
+ const segments = v
125
+ .split("\n")
126
+ .flatMap((l) => l.split(" | "))
127
+ .map((s) => s.replace(/^[-*\d.)\s]+/, "").trim())
128
+ .filter(Boolean);
129
+ return segments.filter((s) => PLACEHOLDER_LINE.test(s)).length >= 2;
130
+ }
131
+ export function filterCustomFields(fields, overrides = {}) {
132
+ const ovByKey = {};
133
+ for (const [k, v] of Object.entries(overrides))
134
+ ovByKey[k.toLowerCase()] = v;
135
+ const out = [];
136
+ for (const cf of fields) {
137
+ const ov = ovByKey[cf.id.toLowerCase()] ?? ovByKey[cf.name.toLowerCase()];
138
+ if (ov && ov.toLowerCase() === "exclude")
139
+ continue;
140
+ if (ov) {
141
+ out.push({ ...cf, bucket: ov });
142
+ continue;
143
+ }
144
+ if (isBoilerplateValue(cf.value))
145
+ continue;
146
+ if (ESSENTIAL_LABEL.test(cf.name)) {
147
+ out.push(cf);
148
+ continue;
149
+ }
150
+ if (DOC_TOOL_URL.test(cf.value)) {
151
+ out.push({ ...cf, bucket: "docs" });
152
+ continue;
153
+ }
154
+ if (CARD_CONFIG_LABEL.test(cf.name))
155
+ continue;
156
+ if (isTemplateValue(cf.value))
157
+ continue;
158
+ out.push(cf);
159
+ }
160
+ return out;
161
+ }
162
+ export function acceptanceLines(value) {
163
+ const lines = toLines(value);
164
+ const isStart = (l) => /^(?:cen[áa]rio|scenario)\b/i.test(l);
165
+ const starts = lines.filter(isStart).length;
166
+ const hasGherkin = lines.some((l) => /^(?:dado|quando|ent[ãa]o|given|when|then)\b/i.test(l));
167
+ if (starts === 0 || (starts === 1 && !hasGherkin))
168
+ return lines;
169
+ const blocks = [];
170
+ for (const l of lines) {
171
+ if (isStart(l) || blocks.length === 0)
172
+ blocks.push([l]);
173
+ else
174
+ blocks[blocks.length - 1].push(l);
175
+ }
176
+ return blocks.map((b) => b.join(" "));
177
+ }
68
178
  export function flattenFieldValue(raw) {
69
179
  if (raw == null)
70
180
  return "";
71
181
  if (typeof raw === "string")
72
182
  return raw.trim();
183
+ if (typeof raw === "number" || typeof raw === "boolean")
184
+ return String(raw);
73
185
  if (Array.isArray(raw)) {
74
186
  return raw.map(flattenFieldValue).filter(Boolean).join("\n").trim();
75
187
  }
@@ -113,11 +225,7 @@ export function extractComments(fields) {
113
225
  return out;
114
226
  }
115
227
  export function mapIssueToSpecContext(issue, opts = {}) {
116
- const overrides = opts.fieldBuckets ?? {};
117
- const custom = (issue.customFields ?? []).map((cf) => {
118
- const ov = overrides[cf.id] ?? overrides[cf.name.toLowerCase()];
119
- return ov ? { ...cf, bucket: ov } : cf;
120
- });
228
+ const custom = filterCustomFields(issue.customFields ?? [], opts.fieldBuckets);
121
229
  const ac = [];
122
230
  const seen = new Set();
123
231
  const pushAc = (line) => {
@@ -130,13 +238,19 @@ export function mapIssueToSpecContext(issue, opts = {}) {
130
238
  issue.acceptanceCriteria.forEach(pushAc);
131
239
  for (const cf of custom)
132
240
  if (cf.bucket === "acceptance")
133
- toLines(cf.value).forEach(pushAc);
241
+ acceptanceLines(cf.value).forEach(pushAc);
134
242
  const sections = custom
135
243
  .filter((cf) => cf.bucket !== "acceptance")
136
244
  .map((cf) => ({ title: cf.name, body: cf.value, bucket: cf.bucket }));
245
+ const images = (issue.attachments ?? []).filter((a) => /^image\//i.test(a.mimeType));
137
246
  const ticketBody = [
138
247
  issue.description,
139
248
  ...sections.map((s) => `### ${s.title}\n${s.body}`),
249
+ images.length
250
+ ? `### Attachments (images)\n${images
251
+ .map((a) => `- [${a.filename}](${a.url})`)
252
+ .join("\n")}`
253
+ : "",
140
254
  ]
141
255
  .filter((s) => s && s.trim())
142
256
  .join("\n\n");
@@ -175,6 +289,18 @@ export function flattenAdf(node) {
175
289
  if (url)
176
290
  return type === "inlineCard" ? url : `${url}\n`;
177
291
  }
292
+ if (type === "taskItem") {
293
+ const attrs = isRecord(node["attrs"]) ? node["attrs"] : {};
294
+ if (attrs["state"] !== "DONE")
295
+ return "";
296
+ return `- [x] ${flattenAdf(node["content"]).trim()}\n`;
297
+ }
298
+ if (type === "tableRow") {
299
+ const cells = Array.isArray(node["content"])
300
+ ? node["content"].map((c) => flattenAdf(c).trim().replace(/\s*\n\s*/g, " · "))
301
+ : [];
302
+ return `${cells.join(" | ")}\n`;
303
+ }
178
304
  const inner = flattenAdf(node["content"]);
179
305
  const blockTypes = new Set([
180
306
  "paragraph",
@@ -184,6 +310,8 @@ export function flattenAdf(node) {
184
310
  "listItem",
185
311
  "codeBlock",
186
312
  "blockquote",
313
+ "table",
314
+ "taskList",
187
315
  ]);
188
316
  if (type === "hardBreak")
189
317
  return "\n";
@@ -256,13 +384,17 @@ function resolveUrl(issue, fields, key, baseUrl) {
256
384
  if (direct)
257
385
  return direct;
258
386
  const self = str(issue["self"]);
259
- if (self) {
387
+ const selfOrigin = (() => {
260
388
  try {
261
- return `${new URL(self).origin}/browse/${key}`;
389
+ const u = new URL(self);
390
+ return u.hostname === "api.atlassian.com" ? undefined : u.origin;
262
391
  }
263
392
  catch {
393
+ return undefined;
264
394
  }
265
- }
395
+ })();
396
+ if (selfOrigin)
397
+ return `${selfOrigin}/browse/${key}`;
266
398
  if (baseUrl) {
267
399
  try {
268
400
  return `${new URL(baseUrl).origin}/browse/${key}`;
@@ -270,6 +402,13 @@ function resolveUrl(issue, fields, key, baseUrl) {
270
402
  catch {
271
403
  }
272
404
  }
405
+ if (self) {
406
+ try {
407
+ return `${new URL(self).origin}/browse/${key}`;
408
+ }
409
+ catch {
410
+ }
411
+ }
273
412
  return undefined;
274
413
  }
275
414
  function nestedName(v) {
@@ -90,6 +90,7 @@ export async function connectJira(opts = {}) {
90
90
  }
91
91
  return makeJiraClient(await clientToToolCaller(client), {
92
92
  dialect: ROVO_DIALECT,
93
+ ...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}),
93
94
  });
94
95
  }
95
96
  export async function connectMcpServer(cfg) {