@guilhermefsousa/open-spec-kit 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 (31) hide show
  1. package/README.md +57 -0
  2. package/bin/open-spec-kit.js +39 -0
  3. package/package.json +51 -0
  4. package/src/commands/doctor.js +324 -0
  5. package/src/commands/init.js +981 -0
  6. package/src/commands/update.js +168 -0
  7. package/src/commands/validate.js +599 -0
  8. package/src/parsers/markdown-sections.js +271 -0
  9. package/src/schemas/projects.schema.js +111 -0
  10. package/src/schemas/spec.schema.js +643 -0
  11. package/templates/agents/agents/spec-hub.agent.md +99 -0
  12. package/templates/agents/rules/hub_structure.instructions.md +49 -0
  13. package/templates/agents/rules/ownership.instructions.md +138 -0
  14. package/templates/agents/scripts/notify-gchat.ps1 +99 -0
  15. package/templates/agents/scripts/notify-gchat.sh +131 -0
  16. package/templates/agents/skills/dev-orchestrator/SKILL.md +573 -0
  17. package/templates/agents/skills/discovery/SKILL.md +406 -0
  18. package/templates/agents/skills/setup-project/SKILL.md +452 -0
  19. package/templates/agents/skills/specifying-features/SKILL.md +378 -0
  20. package/templates/github/agents/spec-hub.agent.md +75 -0
  21. package/templates/github/copilot-instructions.md +102 -0
  22. package/templates/github/instructions/hub_structure.instructions.md +33 -0
  23. package/templates/github/instructions/ownership.instructions.md +45 -0
  24. package/templates/github/prompts/dev.prompt.md +19 -0
  25. package/templates/github/prompts/discovery.prompt.md +20 -0
  26. package/templates/github/prompts/nova-feature.prompt.md +19 -0
  27. package/templates/github/prompts/setup.prompt.md +18 -0
  28. package/templates/github/skills/dev-orchestrator/SKILL.md +9 -0
  29. package/templates/github/skills/discovery/SKILL.md +9 -0
  30. package/templates/github/skills/setup-project/SKILL.md +9 -0
  31. package/templates/github/skills/specifying-features/SKILL.md +9 -0
@@ -0,0 +1,99 @@
1
+ ---
2
+ name: spec-agent
3
+ description: "Orquestra o ciclo de especificação: lê contexto do projeto, invoca as skills (/setup, /discovery, /spec, /dev) e coordena agents do Labs quando necessário."
4
+ user-invocable: true
5
+ disable-model-invocation: false
6
+ ---
7
+
8
+ # Spec Agent
9
+
10
+ O spec-agent é o orquestrador do ciclo de especificação. Ele NÃO gera artefatos diretamente — invoca as skills que fazem o trabalho.
11
+
12
+ ## Step 0 — Antes de qualquer trabalho
13
+
14
+ Leia estes arquivos na ordem:
15
+
16
+ 1. `projects.yml` — repos, stack, dependências, Confluence space
17
+ 2. `docs/architecture.md` — estrutura do sistema
18
+ 3. TODOS os arquivos em `docs/lessons/` — aprender com erros passados
19
+ 4. TODOS os arquivos em `docs/decisions/` — decisões técnicas
20
+ 5. Specs recentes em `specs/` — o que já foi feito
21
+
22
+ Nunca gere specs sem ter lido o acima.
23
+
24
+ ## Skills disponíveis
25
+
26
+ | Skill | Comando | O que faz |
27
+ |-------|---------|-----------|
28
+ | Setup | `/setup @space_key` ou `/setup @page_id` | Bootstrap do projeto a partir do Confluence |
29
+ | Discovery | `/discovery` | Análise de demanda, Q&A com PO, gera PRD |
30
+ | Spec | `/spec` | Quebra PRD em specs técnicas (brief, cenários, contratos, tasks) |
31
+ | Dev | `/dev NNN` | Orquestra implementação (TDD, MR, security, docs vivas) |
32
+
33
+ As instruções completas de cada skill estão em `.agents/skills/*/SKILL.md`.
34
+
35
+ ## Responsabilidades do spec-agent vs Labs agents
36
+
37
+ | Responsabilidade | Quem faz |
38
+ |-----------------|----------|
39
+ | Specs (brief, scenarios, contracts, tasks, links) | **spec-agent** via `/spec` |
40
+ | ADRs (`docs/decisions/`) | **spec-agent** |
41
+ | Design Document (DD) no Confluence | **`design-doc`** (Labs agent) — fallback: spec-agent via MCP direto |
42
+ | Implementação de código | **`dotnet-engineer` / `nodejs-engineer` / `java-engineer`** (Labs) |
43
+ | Security scan | **`labs-secops-agent`** (Labs) |
44
+ | Code review automatizado | **`labs-code-reviewer`** (Labs) |
45
+ | Trade-offs arquiteturais | **`principal-engineer`** (Labs) |
46
+
47
+ ## Rules
48
+
49
+ 1. Leia contexto existente antes de criar qualquer coisa
50
+ 2. Nunca invente contexto de negócio — pergunte ao dev
51
+ 3. Brief máx 1 página
52
+ 4. Cenários usam Given/When/Then
53
+ 5. Tasks agrupadas por repo (ver `projects.yml`)
54
+ 6. Cada task = 1 PR
55
+ 7. Todo conteúdo em Português (pt-BR) com acentuação correta, termos técnicos em inglês
56
+ 8. Aplique lições de `docs/lessons/`
57
+ 9. links.md deve linkar pra feature page no Confluence
58
+
59
+ ## Confluence (contexto)
60
+
61
+ ```
62
+ Projeto (Space ou página raiz)
63
+ ├── 🎯 Visão do Produto ← /setup gera
64
+ ├── 📖 Glossário ← /setup gera | /discovery e /dev atualizam
65
+ ├── 📐 DD (Design Document) ← design-doc agent gera | /dev atualiza
66
+ ├── Demandas/ ← PO joga docs aqui
67
+ ├── 🚀 Features/ ← /setup cria | /discovery escreve dúvidas + PRD
68
+ ├── 🏛️ Domínio/ ← /setup gera | /dev atualiza
69
+ │ ├── 📏 Regras
70
+ │ ├── 🔀 Fluxos
71
+ │ ├── 📊 Tabelas de Referência
72
+ │ └── 🔌 Integrações
73
+ └── 📦 Arquivados/
74
+ ```
75
+
76
+ ## Spec repo (estrutura)
77
+
78
+ ```
79
+ specs/NNN-nome/
80
+ brief.md ← problema + solução (1 página)
81
+ contracts.md ← schemas, tipos, regras
82
+ scenarios.md ← Given/When/Then (viram testes)
83
+ tasks.md ← tarefas por repo
84
+ links.md ← PRs + link da feature page
85
+ audit-report.md ← resultado do self-review do /spec
86
+ conformance-report.json ← validação pós-implementação do /dev
87
+ ```
88
+
89
+ ## Validation checklist
90
+
91
+ - [ ] Brief máx 1 página
92
+ - [ ] Todo cenário tem Given/When/Then
93
+ - [ ] Cenários de falha cobertos
94
+ - [ ] Tasks agrupadas por repo (per projects.yml)
95
+ - [ ] Dependências explícitas
96
+ - [ ] Cada task = 1 PR
97
+ - [ ] contracts.md completo
98
+ - [ ] Lições aplicadas
99
+ - [ ] links.md tem link do Confluence
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: project-structure
3
+ description: Rules for maintaining the spec repo structure and conventions.
4
+ applyTo: '**/*.md'
5
+ ---
6
+
7
+ # Rule: Spec Repo Structure
8
+
9
+ ## Directory conventions
10
+
11
+ - Specs live under `specs/NNN-short-name/` (sequential numbering)
12
+ - Architecture docs live under `docs/architecture.md`
13
+ - ADRs live under `docs/decisions/ADR-NNN-title.md`
14
+ - Lessons live under `docs/lessons/NNN-short-title.md`
15
+ - Project repo references live in `projects.yml`
16
+
17
+ ## Naming
18
+
19
+ - Spec directories: `NNN-short-description` (`001-consumer-shipment`)
20
+ - ADR files: `ADR-NNN-short-title.md` (`ADR-001-multi-repo.md`)
21
+ - Lesson files: `NNN-short-title.md` (`001-pool-postgres.md`)
22
+
23
+ ## Spec lifecycle
24
+
25
+ 1. Created: spec directory with brief + scenarios + contracts + tasks
26
+ 2. In progress: tasks are checked off, links.md tracks PRs
27
+ 3. Done: all tasks checked, spec directory stays (it IS the history)
28
+
29
+ Specs are NOT archived or moved. The spec directory with checked tasks IS the record.
30
+
31
+ ## What goes where
32
+
33
+ | Content | Location |
34
+ |---------|----------|
35
+ | Feature specification | `specs/<spec>/brief.md` |
36
+ | Behavioral scenarios | `specs/<spec>/scenarios.md` |
37
+ | Contract definitions | `specs/<spec>/contracts.md` |
38
+ | Implementation tasks | `specs/<spec>/tasks.md` |
39
+ | PR tracking | `specs/<spec>/links.md` |
40
+ | Spec quality audit | `specs/<spec>/audit-report.md` |
41
+ | Implementation conformance | `specs/<spec>/conformance-report.json` |
42
+ | Architecture overview | `docs/architecture.md` |
43
+ | Architecture decisions | `docs/decisions/ADR-NNN.md` |
44
+ | Lessons learned | `docs/lessons/NNN-title.md` |
45
+ | Repo references | `projects.yml` |
46
+
47
+ ## How contracts work
48
+
49
+ Contract schemas are DEFINED here in `contracts.md` within each spec directory (fields, types, rules, code examples for the project's stack). Code repos IMPLEMENT locally following contracts.md as source of truth.
@@ -0,0 +1,138 @@
1
+ ---
2
+ name: artifact-ownership
3
+ description: RACI matrix defining who creates, reads, updates, and validates each artifact. Prevents duplicate responsibility.
4
+ applyTo: '**/*.md'
5
+ ---
6
+
7
+ # Rule: Artifact Ownership (RACI)
8
+
9
+ Every artifact in this project has exactly **one Owner** (creates it) and at most **one Updater** (can modify it after creation). Other skills may **Read** or **Validate**, but never **Write**.
10
+
11
+ This rule exists to prevent the "duplicate responsibility" anti-pattern where multiple skills update the same artifact with no clear authority, causing drift and conflicts.
12
+
13
+ ## Confluence Artifacts
14
+
15
+ | Artifact | Owner (creates) | Updater | Readers | Validators |
16
+ |----------|----------------|---------|---------|------------|
17
+ | Demandas/ labels | /setup | — | /discovery | — |
18
+ | Visão do Produto | /setup | — | all | — |
19
+ | **Glossário** | /setup | **/discovery** (adds domain terms found in PRD analysis, exit gate), **/dev** (post-merge only — adds terms that emerged during implementation and weren't in the PRD) | /spec | /spec (read-only validate — if terms are missing, stop and ask dev to re-run /discovery) |
20
+ | DD (Design Document) | /spec (via `design-doc` agent, fallback: MCP direct) | /dev (via `design-doc` agent, fallback: MCP direct) | — | — |
21
+ | Domínio/Regras | /setup | /dev (post-merge only) | /discovery, /spec | — |
22
+ | Domínio/Fluxos | /setup | /dev (post-merge only) | /discovery | — |
23
+ | Domínio/Tabelas | /setup | — | /discovery | — |
24
+ | Domínio/Integrações | /setup | /dev (post-merge only) | /discovery | — |
25
+ | Features/ (parent) | /setup | — | all | — |
26
+ | Feature pages (content) | /setup + /discovery | /discovery | /spec, /dev | — |
27
+ | Feature labels | each skill at its transition | — | all | — |
28
+ | Arquivados/ | /setup | — | — | — |
29
+
30
+ ## Local Artifacts
31
+
32
+ | Artifact | Owner (creates) | Updater | Readers | Validators |
33
+ |----------|----------------|---------|---------|------------|
34
+ | `projects.yml` | /setup | /spec (adds planned repos), /dev (repo URLs + status) | all | — |
35
+ | `docs/architecture.md` | /setup | /discovery (resolves decisions) | /spec, /dev | /spec (validates no blockers) |
36
+ | `docs/lessons/` | /dev | /dev | all | — |
37
+ | `docs/decisions/` | manual / spec-agent | — | all | — |
38
+ | `specs/NNN/brief.md` | /spec | — | /dev | — |
39
+ | `specs/NNN/scenarios.md` | /spec | — | /dev | — |
40
+ | `specs/NNN/contracts.md` | /spec | — | /dev | — |
41
+ | `specs/NNN/tasks.md` | /spec | — | /dev | — |
42
+ | `specs/NNN/links.md` | /spec (template) | /dev (MR links) | — | — |
43
+ | `specs/NNN/audit-report.md` | /spec (self-review) | — | /dev (reads before Phase B, blocks if ❌) | — |
44
+ | `specs/NNN/conformance-report.json` | /dev (Phase C.3) | — | CI Guard | — |
45
+
46
+ ## Key Rules
47
+
48
+ 1. **One Writer per artifact** — if two skills both write to the same artifact, one of them is wrong. The matrix above is the source of truth.
49
+
50
+ 2. **Validate ≠ Update** — /spec validates the Glossário (checks all terms are present) but does NOT add terms. If terms are missing, it stops and asks the dev to re-run /discovery.
51
+
52
+ 3. **Post-merge = reality** — when /dev updates Confluence post-merge, it reflects what WAS IMPLEMENTED (merged code), not what was planned. If the implementation diverged from the spec, Confluence must match the code, not the spec.
53
+
54
+ 4. **DD ownership** — the Design Document is owned by the `design-doc` agent (Labs). Skills delegate to it. Only use direct MCP as a fallback when the agent is unavailable.
55
+
56
+ 5. **Labels accumulate** — MCP Confluence only adds labels (no remove). The "current" workflow state is always the most recently added label. Previous labels stay as history.
57
+
58
+ **Label state resolution algorithm:** to determine the current state when multiple labels exist, compare label creation timestamps (via metadata da página no Confluence). The label with the highest creation timestamp defines the current state. If timestamps are not available, use the following precedence order (highest wins): `done` > `em-dev` > `em-spec` > `prd-aprovado` > `prd-rejeitado` > `prd-review` > `aguardando-po` > `em-discovery`.
59
+
60
+ 6. **Dual-platform sync (Claude + Copilot)** — this project supports both Claude Code and GitHub Copilot. When modifying agent definitions, rules, or skills, you MUST update both platforms:
61
+
62
+ | Source of truth (`.agents/`) | Copilot mirror (`.github/`) |
63
+ |------------------------------|----------------------------|
64
+ | `.agents/agents/spec-hub.agent.md` | `.github/agents/spec-hub.agent.md` (update `tools`, `agents`, `mcp-servers` in frontmatter) |
65
+ | `.agents/rules/ownership.instructions.md` | `.github/instructions/ownership.instructions.md` (update summary tables) |
66
+ | `.agents/rules/hub_structure.instructions.md` | `.github/instructions/hub_structure.instructions.md` (update structure tables) |
67
+ | `.agents/skills/*/SKILL.md` | `.github/prompts/*.prompt.md` (update flow steps if changed) |
68
+ | `.mcp.json` | `.vscode/mcp.json` (update server config — different format: `servers` + `type` field) |
69
+
70
+ **`.agents/` is ALWAYS the source of truth.** The `.github/` files are Copilot-compatible mirrors. If in doubt, read `.agents/` first.
71
+
72
+ Changes that require sync:
73
+ - Adding/removing a sub-agent → update `.github/agents/spec-hub.agent.md` frontmatter `agents:` list + `.github/copilot-instructions.md` sub-agents table
74
+ - Adding/removing a skill → create/update `.github/prompts/` prompt file + `.github/copilot-instructions.md` skills table
75
+ - Changing RACI matrix → update `.github/instructions/ownership.instructions.md` summary
76
+ - Changing MCP servers → update `.vscode/mcp.json` (format: `servers` + `type: "stdio"`)
77
+
78
+ ## Label Transitions
79
+
80
+ ### Demandas/ labels (owned by /setup)
81
+ ```
82
+ (none) → novo → processando → processado
83
+ ```
84
+
85
+ ### Feature labels (sequential flow)
86
+ ```
87
+ em-discovery → aguardando-po → prd-review → prd-aprovado → em-spec → em-dev → done
88
+ ↑ |
89
+ └──── prd-rejeitado ─────────┘
90
+ ```
91
+
92
+ - `prd-aprovado` is added by the **PO manually** (human gate)
93
+ - `prd-rejeitado` is added by the **PO manually** (triggers /discovery re-run)
94
+ - All other labels are added by the respective skill automatically
95
+
96
+ ## MCP Failure Recovery
97
+
98
+ All 4 skills must handle MCP failures:
99
+ - /setup: re-execution checks existing pages, creates only missing ones
100
+ - /discovery: saves pending operations to `docs/pending-confluence-updates.md`
101
+ - /spec: saves pending operations to `docs/pending-confluence-updates.md`
102
+ - /dev: saves pending updates to `docs/pending-confluence-updates.md`, retries on next execution
103
+
104
+ ## Architecture Decisions Format
105
+
106
+ The `docs/architecture.md` "Decisões em Aberto" table uses this schema:
107
+
108
+ ```
109
+ | # | Decisão | Impacto | Bloqueia | Status |
110
+ ```
111
+
112
+ Status values: `aberta`, `resolvida — [decisão tomada]`
113
+
114
+ Only /discovery updates this column (exit gate). /spec validates that no `aberta` decision blocks /dev.
115
+
116
+ ## links.md PR Table Format
117
+
118
+ ```markdown
119
+ ## PRs
120
+
121
+ | Repo | Branch | MR/PR | Status |
122
+ |------|--------|-------|--------|
123
+ ```
124
+
125
+ Status values: `open`, `em-review`, `merged`, `closed`. Created empty by /spec, filled by /dev.
126
+
127
+ ## Shared Types Between Features
128
+
129
+ When `contracts.md` defines types that other features will reuse (e.g., ErrorResponse, PaginatedResponse):
130
+ - Mark them in a `## Tipos compartilhados` section
131
+ - Features that USE types from another feature must REFERENCE them ("Ver contracts.md da feature NNN") — never redefine
132
+
133
+ ## Feature Scope Thresholds
134
+
135
+ Both /discovery and /spec use the same criteria:
136
+ - More than **15 behaviors/requirements** → flag as potentially large
137
+ - Spans **4+ repos** → flag as potentially large
138
+ - This is a suggestion, not a blocker. The PO/dev decides whether to break it up.
@@ -0,0 +1,99 @@
1
+ # notify-gchat.ps1 — Envia notificações ao Google Chat via webhook (Windows/PowerShell)
2
+ #
3
+ # Uso simples:
4
+ # .\scripts\notify-gchat.ps1 -Text "Mensagem simples"
5
+ #
6
+ # Uso com card:
7
+ # .\scripts\notify-gchat.ps1 -Card `
8
+ # -Title "✅ Título" `
9
+ # -Subtitle "Nome do Projeto" `
10
+ # -Body "Texto principal." `
11
+ # -Next "Próximo passo" `
12
+ # -ButtonText "Ver no Confluence" `
13
+ # -ButtonUrl "https://..."
14
+
15
+ param(
16
+ [switch]$Card,
17
+ [string]$Title = "",
18
+ [string]$Subtitle = "",
19
+ [string]$Body = "",
20
+ [string]$Next = "",
21
+ [string]$ButtonText = "",
22
+ [string]$ButtonUrl = "",
23
+ [string]$Text = ""
24
+ )
25
+
26
+ # Auto-load .env from project root
27
+ $envFile = Join-Path $PSScriptRoot ".." ".env"
28
+ if (Test-Path $envFile) {
29
+ Get-Content $envFile | ForEach-Object {
30
+ if ($_ -match '^\s*([^#][^=]+)=(.*)$') {
31
+ $name = $Matches[1].Trim()
32
+ $value = $Matches[2].Trim()
33
+ if (-not [Environment]::GetEnvironmentVariable($name)) {
34
+ [Environment]::SetEnvironmentVariable($name, $value, "Process")
35
+ }
36
+ }
37
+ }
38
+ }
39
+
40
+ $webhookUrl = $env:GCHAT_WEBHOOK_URL
41
+ if (-not $webhookUrl) {
42
+ Write-Host "[notify-gchat] GCHAT_WEBHOOK_URL nao configurada — notificacao ignorada" -ForegroundColor Yellow
43
+ exit 0
44
+ }
45
+
46
+ if ($Card) {
47
+ $header = @{ title = $Title }
48
+ if ($Subtitle) { $header.subtitle = $Subtitle }
49
+
50
+ $sections = @()
51
+
52
+ if ($Body) {
53
+ $sections += @{ widgets = @(@{ textParagraph = @{ text = $Body } }) }
54
+ }
55
+ if ($Next) {
56
+ $sections += @{
57
+ widgets = @(@{
58
+ decoratedText = @{
59
+ startIcon = @{ knownIcon = "FLIGHT_ARRIVAL" }
60
+ text = "<b>Próximo passo</b>: $Next"
61
+ }
62
+ })
63
+ }
64
+ }
65
+ if ($ButtonText -and $ButtonUrl) {
66
+ $sections += @{
67
+ widgets = @(@{
68
+ buttonList = @{
69
+ buttons = @(@{
70
+ text = $ButtonText
71
+ onClick = @{ openLink = @{ url = $ButtonUrl } }
72
+ })
73
+ }
74
+ })
75
+ }
76
+ }
77
+
78
+ $card = @{ header = $header }
79
+ if ($sections.Count -gt 0) { $card.sections = $sections }
80
+ $payload = @{ cardsV2 = @(@{ cardId = "open-spec-kit-notification"; card = $card }) }
81
+ } else {
82
+ $payload = @{ text = $Text }
83
+ }
84
+
85
+ try {
86
+ $json = $payload | ConvertTo-Json -Depth 10 -Compress
87
+ $bytes = [System.Text.Encoding]::UTF8.GetBytes($json)
88
+ $response = Invoke-WebRequest -Uri $webhookUrl -Method POST `
89
+ -ContentType "application/json; charset=UTF-8" `
90
+ -Body $bytes -UseBasicParsing -SkipCertificateCheck -TimeoutSec 10
91
+
92
+ if ($response.StatusCode -ne 200) {
93
+ Write-Host "[notify-gchat] Resposta inesperada: $($response.StatusCode)" -ForegroundColor Yellow
94
+ }
95
+ } catch {
96
+ Write-Host "[notify-gchat] Falha ao enviar notificacao — continuando normalmente" -ForegroundColor Yellow
97
+ }
98
+
99
+ exit 0
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # notify-gchat.sh — Send notifications to Google Chat via webhook
4
+ #
5
+ # Usage:
6
+ # ./scripts/notify-gchat.sh "Simple text message"
7
+ # ./scripts/notify-gchat.sh --card \
8
+ # --title "✅ Título" \
9
+ # --subtitle "Nome do Projeto" \
10
+ # --body "Texto principal.<br>Com quebras de linha." \
11
+ # --next "Próximo passo aqui" \
12
+ # --button-text "Ver no Confluence" \
13
+ # --button-url "https://..."
14
+
15
+ # Auto-load .env from project root
16
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17
+ ENV_FILE="$SCRIPT_DIR/../.env"
18
+ if [ -f "$ENV_FILE" ] && [ -z "$GCHAT_WEBHOOK_URL" ]; then
19
+ set -a
20
+ source "$ENV_FILE"
21
+ set +a
22
+ fi
23
+
24
+ if [ -z "$GCHAT_WEBHOOK_URL" ]; then
25
+ echo "[notify-gchat] GCHAT_WEBHOOK_URL não configurada — notificação ignorada" >&2
26
+ exit 0
27
+ fi
28
+
29
+ CARD_MODE=false
30
+ TITLE=""
31
+ SUBTITLE=""
32
+ BODY=""
33
+ NEXT=""
34
+ BUTTON_TEXT=""
35
+ BUTTON_URL=""
36
+ TEXT_MESSAGE=""
37
+
38
+ if [ "$1" = "--card" ]; then
39
+ CARD_MODE=true
40
+ shift
41
+ while [ $# -gt 0 ]; do
42
+ case "$1" in
43
+ --title) TITLE="$2"; shift 2 ;;
44
+ --subtitle) SUBTITLE="$2"; shift 2 ;;
45
+ --body) BODY="$2"; shift 2 ;;
46
+ --next) NEXT="$2"; shift 2 ;;
47
+ --button-text) BUTTON_TEXT="$2"; shift 2 ;;
48
+ --button-url) BUTTON_URL="$2"; shift 2 ;;
49
+ *) shift ;;
50
+ esac
51
+ done
52
+ else
53
+ TEXT_MESSAGE="$*"
54
+ fi
55
+
56
+ # Build and send via Python — avoids bash UTF-8 corruption
57
+ PYTHONIOENCODING=utf-8 python -c "
58
+ import json, sys, subprocess
59
+
60
+ card_mode = sys.argv[1] == 'true'
61
+ webhook_url = sys.argv[9]
62
+
63
+ if card_mode:
64
+ title = sys.argv[2]
65
+ subtitle = sys.argv[3]
66
+ body = sys.argv[4]
67
+ next_step = sys.argv[5]
68
+ btn_text = sys.argv[6]
69
+ btn_url = sys.argv[7]
70
+
71
+ header = {'title': title}
72
+ if subtitle:
73
+ header['subtitle'] = subtitle
74
+
75
+ sections = []
76
+
77
+ # Section 1: body (main content)
78
+ if body:
79
+ sections.append({
80
+ 'widgets': [{'textParagraph': {'text': body}}]
81
+ })
82
+
83
+ # Section 2: next step (highlighted)
84
+ if next_step:
85
+ sections.append({
86
+ 'widgets': [{
87
+ 'decoratedText': {
88
+ 'startIcon': {'knownIcon': 'FLIGHT_ARRIVAL'},
89
+ 'text': '<b>Próximo passo</b>: ' + next_step
90
+ }
91
+ }]
92
+ })
93
+
94
+ # Section 3: button
95
+ if btn_text and btn_url:
96
+ sections.append({
97
+ 'widgets': [{
98
+ 'buttonList': {
99
+ 'buttons': [{
100
+ 'text': btn_text,
101
+ 'onClick': {'openLink': {'url': btn_url}}
102
+ }]
103
+ }
104
+ }]
105
+ })
106
+
107
+ card = {'header': header}
108
+ if sections:
109
+ card['sections'] = sections
110
+
111
+ payload = {'cardsV2': [{'cardId': 'open-spec-kit-notification', 'card': card}]}
112
+ else:
113
+ text = sys.argv[8]
114
+ payload = {'text': text}
115
+
116
+ data = json.dumps(payload, ensure_ascii=False).encode('utf-8')
117
+
118
+ result = subprocess.run(
119
+ ['curl', '-s', '-o', '/dev/null', '--max-time', '10',
120
+ '-X', 'POST', webhook_url,
121
+ '-H', 'Content-Type: application/json; charset=UTF-8',
122
+ '-d', '@-'],
123
+ input=data,
124
+ capture_output=True
125
+ )
126
+
127
+ if result.returncode != 0:
128
+ print('[notify-gchat] Falha ao enviar notificação — continuando normalmente', file=sys.stderr)
129
+ " "$CARD_MODE" "$TITLE" "$SUBTITLE" "$BODY" "$NEXT" "$BUTTON_TEXT" "$BUTTON_URL" "$TEXT_MESSAGE" "$GCHAT_WEBHOOK_URL" 2>/dev/null
130
+
131
+ exit 0