@guilhermefsousa/open-spec-kit 0.0.1
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.
- package/README.md +57 -0
- package/bin/open-spec-kit.js +39 -0
- package/package.json +51 -0
- package/src/commands/doctor.js +324 -0
- package/src/commands/init.js +981 -0
- package/src/commands/update.js +210 -0
- package/src/commands/validate.js +615 -0
- package/src/parsers/markdown-sections.js +271 -0
- package/src/schemas/projects.schema.js +111 -0
- package/src/schemas/spec.schema.js +760 -0
- package/templates/agents/agents/spec-hub.agent.md +99 -0
- package/templates/agents/rules/hub_structure.instructions.md +49 -0
- package/templates/agents/rules/ownership.instructions.md +138 -0
- package/templates/agents/scripts/notify-gchat.ps1 +99 -0
- package/templates/agents/scripts/notify-gchat.sh +131 -0
- package/templates/agents/skills/dev-orchestrator/SKILL.md +573 -0
- package/templates/agents/skills/discovery/SKILL.md +406 -0
- package/templates/agents/skills/setup-project/SKILL.md +459 -0
- package/templates/agents/skills/specifying-features/SKILL.md +379 -0
- package/templates/github/agents/spec-hub.agent.md +75 -0
- package/templates/github/copilot-instructions.md +102 -0
- package/templates/github/instructions/hub_structure.instructions.md +33 -0
- package/templates/github/instructions/ownership.instructions.md +45 -0
- package/templates/github/prompts/dev.prompt.md +19 -0
- package/templates/github/prompts/discovery.prompt.md +20 -0
- package/templates/github/prompts/nova-feature.prompt.md +19 -0
- package/templates/github/prompts/setup.prompt.md +18 -0
- package/templates/github/skills/dev-orchestrator/SKILL.md +9 -0
- package/templates/github/skills/discovery/SKILL.md +9 -0
- package/templates/github/skills/setup-project/SKILL.md +9 -0
- 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
|