Title: Add ProtoAgent behavior prototyping tool with loyalty and travel system examples

Key features implemented:
- New pkg/protoagent module with core engine, generators, and policy creators
- README documentation covering usage, types, and development workflow
- Example configurations for cafeteria loyalty system and travel experience platform
- Generated artifacts including agent configs, DB schemas, interfaces, and OPA policies
- Integration with PicoClaw workspace structure and MCP protocol
- Validation reports and comprehensive error handling

The ProtoAgent tool now enables rapid prototyping of agent behaviors from structured requirements, demonstrated through practical examples that generate complete system configurations including security policies, database schemas, and communication channels.
This commit is contained in:
qwen.ai[bot] 2026-04-12 18:45:04 +00:00
parent 6d03791929
commit 05a53bfcdf
13 changed files with 3505 additions and 64 deletions

120
.gitignore vendored
View file

@ -1,71 +1,69 @@
# Binaries
# Go build artifacts
bin/
build/
*.exe
*.dll
*.so
*.dylib
*.test
```
# Build artifacts
*.o
*.obj
*.out
/picoclaw
/picoclaw-test
cmd/**/workspace
*.so
*.dll
*.exe
*.a
*.dylib
# Picoclaw specific
# Go-specific
*.test
*.prof
cover.out
coverage.txt
# PicoClaw
.picoclaw/
config.json
sessions/
build/
# Dependencies
vendor/
# Logs
*.log
# Temporary files
*.tmp
*~
.DS_Store
Thumbs.db
# Environment
.env
.env.local
*.env.*
# Coverage
# Secrets & Config (keep templates, ignore actual secrets)
.env
config/config.json
.security.yml
onboard
# Test
coverage.txt
coverage.html
# OS
.DS_Store
# Ralph workspace
ralph/
.ralph/
tasks/
# Plans
docs/plans/
docs/superpowers/
coverage/
htmlcov/
.coverage
# Editors
.vscode/
.idea/
*.swp
*.swo
# Added by goreleaser init:
dist/
*.vite/
# Windows Application Icon/Resource
*.syso
# Test telegram integration
cmd/telegram/
# Keep embedded backend dist directory placeholder in VCS
!web/backend/dist/
web/backend/dist/*
!web/backend/dist/.gitkeep
.claude/
docker/data
.omc/
# Archives
*.zip
*.gz
*.tar
*.tgz
*.bz2
*.xz
*.7z
*.rar
*.zst
*.lz4
*.lzh
*.cab
*.arj
*.rpm
*.deb
*.Z
*.lz
*.lzo
*.tar.gz
*.tar.bz2
*.tar.xz
*.tar.zst
```

View file

@ -0,0 +1,561 @@
# Saída Esperada dos Exemplos ProtoAgent
Este documento descreve a saída esperada ao executar os exemplos do ProtoAgent.
## 📦 Exemplo A: Sistema de Fidelização de Cafeteria
### Comando de Execução
```bash
go run examples/protoagent/example_cafeteria.go
```
### Saída Esperada no Terminal
```
📦 Resumo dos Artefatos Gerados:
==================================================
🤖 Agente: Cafeteria Loyalty System
Descrição: Sistema de fidelização de cafeteria onde clientes acumulam 'grãos'...
Tools: [api_client message_handler database_tool]
💾 Schemas de Banco de Dados: 3
- Customer (sql)
Tabela: customers (6 colunas)
- id: uuid [PK] [NOT NULL]
- created_at: timestamp [DEFAULT CURRENT_TIMESTAMP]
- updated_at: timestamp
- name: varchar(255) [NOT NULL]
- phone: varchar(255) [NOT NULL]
- email: varchar(255)
- GrainTransaction (sql)
Tabela: graintransactions (8 colunas)
- id: uuid [PK]
- customer_id: uuid [NOT NULL]
- amount: integer [NOT NULL]
- type: varchar(50) [NOT NULL]
- balance_after: integer [NOT NULL]
- attendant_id: uuid [NOT NULL]
- created_at: timestamp
- Product (sql)
Tabela: products (7 colunas)
- id: uuid [PK]
- name: varchar(255) [NOT NULL]
- grains_cost: integer [NOT NULL]
- quantity: integer [NOT NULL]
- category: varchar(100)
- active: boolean [DEFAULT true]
🖥️ Interfaces: 2
- API (api)
Endpoints: 7
[POST] /api/v1/managecustomer - Gerenciar cadastro de clientes...
[POST] /api/v1/addgrains - Adicionar grãos à conta do cliente...
[POST] /api/v1/redeemgrains - Consumir grãos para resgatar produtos...
[POST] /api/v1/consultbalance - Consultar saldo de grãos...
[POST] /api/v1/manageproducts - Gerenciar catálogo de produtos...
[POST] /api/v1/telegrambalancequery - Permitir consulta via Telegram...
[POST] /api/v1/transactionhistory - Listar histórico de transações...
- Web UI (web)
Telas: 7
📱 ManageCustomer (/managecustomer)
Componentes: 4
📱 AddGrains (/addgrains)
Componentes: 4
...
📱 Canais de Comunicação: 1
- telegram (telegram) - Habilitado: true
Configuração:
token: ${TELEGRAM_BOT_TOKEN}
🔐 Políticas OPA: 3
- rbac_policy (authz.rbac)
Descrição: Role-Based Access Control policy
Código Rego (15 linhas):
package authz.rbac
# Auto-generated RBAC policy from requirements
# Default deny
default allow = false
... (10 linhas restantes)
- authentication_policy (authz.custom)
Descrição: Todos os atendentes devem ser autenticados...
- data_access_policy (authz.data_access)
Descrição: Data access control based on classification levels
🎯 Skills: 2
- addgrains_skill
Descrição: Adicionar grãos à conta do cliente baseado na compra
Triggers: [AddGrains]
- redeemgrains_skill
Descrição: Consumir grãos para resgatar produtos
Triggers: [RedeemGrains]
🔧 Tools: 4
- api_tool (custom)
Descrição: Tool for api interactions
Configuração:
type: http
base_url: ${API_BASE_URL}
- message_handler_tool (custom)
Descrição: Tool for messaging interactions
- database_tool (custom)
Descrição: Tool for database interactions
Configuração:
type: database
driver: postgres
dsn: ${DATABASE_URL}
- webhook_handler_tool (custom)
Descrição: Tool for webhook interactions
✅ Validação: true
Sugestões: 1
💡 Consider testing OPA policies with: opa eval -i input.json -d policy.rego
📄 AGENT.json salvo
📄 Schema Customer salvo (JSON + SQL)
📄 Schema GrainTransaction salvo (JSON + SQL)
📄 Schema Product salvo (JSON + SQL)
📄 Política rbac_policy salva (JSON)
📄 Código Rego rbac_policy salvo
📄 Política authentication_policy salva (JSON)
📄 Código Rego authentication_policy salvo
📄 Política data_access_policy salva (JSON)
📄 Código Rego data_access_policy salvo
📄 channels.json salvo
📄 skills.json salvo
📄 Códigos das skills salvos
📄 tools.json salvo
📄 validation_report.json salvo
✅ Artefatos gerados com sucesso!
```
### Arquivos Gerados em `output/cafeteria/`
```
output/cafeteria/
├── AGENT.json # Configuração do agente em JSON
├── AGENT.md # Configuração do agente em Markdown
├── schema_0_customer.json # Schema do cliente em JSON
├── schema_0_customer.sql # DDL SQL da tabela customers
├── schema_1_graintransaction.json # Schema de transações em JSON
├── schema_1_graintransaction.sql # DDL SQL da tabela graintransactions
├── schema_2_product.json # Schema de produtos em JSON
├── schema_2_product.sql # DDL SQL da tabela products
├── policy_0_rbac_policy.rego.json # Política RBAC em JSON
├── policy_0_rbac_policy.rego # Política RBAC em Rego puro
├── policy_1_authentication_policy.rego.json
├── policy_1_authentication_policy.rego
├── policy_2_data_access_policy.rego.json
├── policy_2_data_access_policy.rego
├── channels.json # Configuração dos canais
├── skills.json # Definições das skills
├── skill_0_addgrains_skill.go # Código Go da skill AddGrains
├── skill_1_redeemgrains_skill.go # Código Go da skill RedeemGrains
├── tools.json # Definições das tools
└── validation_report.json # Relatório de validação
```
### Exemplo de Código Rego Gerado (policy_0_rbac_policy.rego)
```rego
package authz.rbac
# Auto-generated RBAC policy from requirements
# Default deny
default allow = false
# Role definitions
roles := {
"admin",
"attendant",
"customer",
}
# Permission definitions
permissions := {
"read",
"write",
"delete",
"redeem",
"add_grains",
}
# Role-permission mapping
role_permissions := {
"admin": {"read", "write", "delete", "admin"},
"attendant": {"read", "write", "add_grains", "redeem"},
"customer": {"read"}
}
# Allow if user has required permission
allow {
some role in input.user.roles
some perm in role_permissions[role]
perm == input.permission
}
# Admin bypass
allow {
some role in input.user.roles
role == "admin"
}
```
### Exemplo de SQL Gerado (schema_0_customer.sql)
```sql
-- Schema: Customer
-- Type: sql
CREATE TABLE IF NOT EXISTS customers (
id uuid PRIMARY KEY,
created_at timestamp DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp,
name varchar(255) NOT NULL,
phone varchar(255) NOT NULL,
email varchar(255)
);
CREATE TABLE IF NOT EXISTS graintransactions (
id uuid PRIMARY KEY,
created_at timestamp DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp,
customer_id uuid NOT NULL,
amount integer NOT NULL,
type varchar(50) NOT NULL,
balance_after integer NOT NULL,
attendant_id uuid NOT NULL
);
CREATE TABLE IF NOT EXISTS products (
id uuid PRIMARY KEY,
created_at timestamp DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp,
name varchar(255) NOT NULL,
grains_cost integer NOT NULL,
quantity integer NOT NULL,
category varchar(100),
active boolean DEFAULT true
);
```
---
## 📦 Exemplo B: Plataforma de Experiências de Viagem
### Comando de Execução
```bash
go run examples/protoagent/example_travel.go
```
### Saída Esperada no Terminal
```
📦 Resumo dos Artefatos Gerados:
==================================================
🤖 Agente: Travel Experience Platform
Descrição: Plataforma de experiências de viagem onde viajantes compartilham...
Tools: [api_client webhook_handler file_tool]
Skills: [filterpersonaldata_skill enrichstorycontent_skill curatecontent_skill]
💾 Schemas de Banco de Dados: 4
- TravelStory (nosql)
Coleção: travel_stories
Schema: {title: string, content: text, status: string, ...}
- TravelerProfile (nosql)
Coleção: traveler_profiles
- MediaAsset (nosql)
Coleção: media_assets
- SocialMediaPublication (nosql)
Coleção: social_publications
🖥️ Interfaces: 2
- API (api)
Endpoints: 10
[POST] /api/v1/submittavelstory - Submeter relato com mídias
[POST] /api/v1/filterpersonaldata - Filtrar informações pessoais (PII)
[POST] /api/v1/enrichstorycontent - Coletar informações adicionais
[POST] /api/v1/generatesocialmediaarticle - Criar artigos para redes sociais
[POST] /api/v1/curatecontent - Curadoria de conteúdo
[POST] /api/v1/publishtosocialmedia - Publicar em redes sociais
[POST] /api/v1/monitorengagement - Monitorar engajamento
[POST] /api/v1/managetravelerprofile - Gerenciar perfil de viajante
[POST] /api/v1/externalapiintegration - Integrar APIs externas
[POST] /api/v1/updatestory - Atualizar relatos existentes
- Web UI (web)
Telas: 10
📱 SubmitTravelStory (/submittavelstory)
Componentes: 7
📱 CurateContent (/curatecontent)
Componentes: 4
...
📱 Canais de Comunicação: 2
- webhook (webhook) - Habilitado: true
Configuração:
path: /webhook
secret: ${WEBHOOK_SECRET}
- messaging (messaging) - Habilitado: true
Configuração:
provider: ${MESSAGING_PROVIDER}
🔐 Políticas OPA: 4
- rbac_policy (authz.rbac)
Descrição: Role-Based Access Control policy
- privacy_protection_policy (authz.custom)
Descrição: Sistema deve detectar e remover automaticamente PII
- content_moderation_policy (authz.custom)
Descrição: Conteúdo deve passar por moderação antes de publicação
- data_access_policy (authz.data_access)
Descrição: Data access control based on classification levels
🎯 Skills: 5
- filterpersonaldata_skill
Descrição: Filtrar automaticamente informações pessoais dos relatos
Triggers: [FilterPersonalData]
Dependências: [pii_detection_lib]
- enrichstorycontent_skill
Descrição: Coletar informações adicionais do autor
Triggers: [EnrichStoryContent]
- curatecontent_skill
Descrição: Realizar curadoria do conteúdo
Triggers: [CurateContent]
- generatesocialmediaarticle_skill
Descrição: Elaborar artigos formatados para redes sociais
Triggers: [GenerateSocialMediaArticle]
- monitorengagement_skill
Descrição: Monitorar engajamento das publicações
Triggers: [MonitorEngagement]
🔧 Tools: 5
- api_tool (custom)
- webhook_handler_tool (custom)
- file_tool (custom)
- external_api_tool (custom)
Configuração:
type: http
providers: instagram,facebook,twitter,linkedin
- ai_tool (custom)
Configuração:
type: ai
capabilities: pii_detection,content_generation
🔌 Servidores MCP: 1
- default (stdio)
Comando: mcp-server --config ${MCP_CONFIG_PATH}
✅ Validação: true
Sugestões: 2
💡 Enable AI features for content enrichment and curation
💡 Configure external API credentials for social media platforms
📄 AGENT.json e AGENT.md salvos
📄 Schema TravelStory salvo
📄 Schema TravelerProfile salvo
📄 Schema MediaAsset salvo
📄 Schema SocialMediaPublication salvo
📄 Política rbac_policy salva (JSON)
📄 Código Rego rbac_policy salvo
... (outras políticas)
📄 interfaces.json salvo
📄 channels.json salvo
📄 skills.json salvo
📄 Códigos das skills salvos
📄 tools.json salvo
📄 mcp_config.json salvo
📄 validation_report.json salvo
✅ Artefatos gerados com sucesso!
```
### Arquivos Gerados em `output/travel/`
```
output/travel/
├── AGENT.json
├── AGENT.md
├── schema_0_travelstory.json
├── schema_1_travelerprofile.json
├── schema_2_mediaasset.json
├── schema_3_socialpublication.json
├── policy_0_rbac_policy.rego.json
├── policy_0_rbac_policy.rego
├── policy_1_privacy_protection_policy.rego.json
├── policy_1_privacy_protection_policy.rego
├── policy_2_content_moderation_policy.rego.json
├── policy_2_content_moderation_policy.rego
├── policy_3_data_access_policy.rego.json
├── policy_3_data_access_policy.rego
├── interfaces.json
├── channels.json
├── skills.json
├── skill_0_filterpersonaldata_skill.go
├── skill_1_enrichstorycontent_skill.go
├── skill_2_curatecontent_skill.go
├── skill_3_generatesocialmediaarticle_skill.go
├── skill_4_monitorengagement_skill.go
├── tools.json
├── mcp_config.json
└── validation_report.json
```
### Exemplo de Política de Privacidade (policy_1_privacy_protection_policy.rego)
```rego
package authz.custom
# Policy: PrivacyProtection
# Description: Sistema deve detectar e remover automaticamente informações pessoais identificáveis (PII)
default allow = false
# PII detection enabled
pii_detection_enabled {
input.pii_detection == "true"
}
# Auto redaction enabled
auto_redaction_enabled {
input.auto_redaction == "true"
}
# GDPR compliance check
gdpr_compliant {
input.gdpr_compliance == "true"
input.consent_obtained == true
}
allow {
pii_detection_enabled
auto_redaction_enabled
gdpr_compliant
}
```
### Exemplo de Skill Gerada (skill_0_filterpersonaldata_skill.go)
```go
// Auto-generated skill for: FilterPersonalData
package skills
import (
"context"
"regexp"
)
// FilterPersonalDataSkill filters personally identifiable information from content
func FilterPersonalDataSkill(ctx context.Context, params map[string]interface{}) (interface{}, error) {
// Preconditions: Relato deve estar em revisão
content, ok := params["story_content"].(string)
if !ok {
return nil, fmt.Errorf("story_content is required")
}
// Detect PII patterns
patterns := map[string]*regexp.Regexp{
"email": regexp.MustCompile(`[\w\.-]+@[\w\.-]+\.\w+`),
"phone": regexp.MustCompile(`\+?\d[\d\s\-\(\)]{8,}\d`),
"cpf": regexp.MustCompile(`\d{3}\.?\d{3}\.?\d{3}-?\d{2}`),
}
detectedPII := make([]map[string]string, 0)
filteredContent := content
for piiType, pattern := range patterns {
matches := pattern.FindAllString(content, -1)
for _, match := range matches {
detectedPII = append(detectedPII, map[string]string{
"type": piiType,
"value": match,
})
// Redact PII
filteredContent = regexp.MustCompile(regexp.QuoteMeta(match)).
ReplaceAllString(filteredContent, "[REDACTED]")
}
}
result := map[string]interface{}{
"filtered_content": filteredContent,
"detected_pii": detectedPII,
"confidence_score": float64(len(detectedPII)) / float64(len(content)) * 100,
}
return result, nil
}
```
---
## 🔍 Como Testar as Políticas OPA Geradas
### Instalando OPA
```bash
# Linux/Mac
curl -L -o opa https://openpolicyagent.org/downloads/latest/opa_linux_amd64_static
chmod +x opa
sudo mv opa /usr/local/bin/
```
### Testando Política RBAC
Crie um arquivo `input.json`:
```json
{
"user": {
"id": "user123",
"roles": ["attendant"]
},
"permission": "add_grains",
"resource": "grain_transaction"
}
```
Execute:
```bash
cd output/cafeteria
opa eval -i input.json -d policy_0_rbac_policy.rego "data.authz.rbac.allow"
# Resultado esperado: true
```
Teste com permissão não autorizada:
```json
{
"user": {
"id": "customer456",
"roles": ["customer"]
},
"permission": "add_grains"
}
```
```bash
opa eval -i input.json -d policy_0_rbac_policy.rego "data.authz.rbac.allow"
# Resultado esperado: false
```
---
## 🚀 Próximos Passos
1. **Revisar Artefatos**: Analise os arquivos gerados
2. **Customizar**: Ajuste conforme necessidades específicas
3. **Configurar Credenciais**:
- Token do Telegram
- Connection string do banco de dados
- Chaves de API para redes sociais
4. **Implementar Skills**: Complete a lógica de negócio nas skills geradas
5. **Testar Localmente**: Execute o agente em modo de desenvolvimento
6. **Implantar**: Copie artefatos para produção
Para mais detalhes, consulte `examples/protoagent/README.md`.

View file

@ -0,0 +1,266 @@
# Exemplos de Uso do ProtoAgent
Este diretório contém exemplos funcionais de como utilizar o ProtoAgent para prototipar diferentes tipos de sistemas.
## 📋 Visão Geral
O ProtoAgent transforma requisitos funcionais e não-funcionais em artefatos prontos para uso:
- Configurações de agente (AGENT.md)
- Schemas de banco de dados (SQL/NoSQL)
- Interfaces (API, Web UI)
- Canais de comunicação (Telegram, Discord, etc.)
- Políticas OPA (Open Policy Agent)
- Skills personalizadas
- Tools de integração
- Configuração MCP
## 🎯 Exemplos Disponíveis
### a) Sistema de Fidelização de Cafeteria
**Arquivo:** `cafeteria-loyalty-system.json`
Um sistema onde clientes acumulam "grãos" baseados no consumo e podem trocar por produtos.
#### Características Principais:
- **Canal Telegram**: Clientes consultam saldo via bot
- **Atendentes**: Gerenciam contas, adicionam/consumem grãos
- **Catálogo de Produtos**: Resgate com grãos acumulados
- **Histórico de Transações**: Auditoria completa
#### Requisitos Funcionais:
1. `ManageCustomer` - Cadastro de clientes
2. `AddGrains` - Adicionar grãos por compra
3. `RedeemGrains` - Resgatar produtos
4. `ConsultBalance` - Consultar saldo
5. `ManageProducts` - Gerenciar catálogo
6. `TelegramBalanceQuery` - Consulta via Telegram
7. `TransactionHistory` - Histórico de transações
#### Requisitos Não-Funcionais:
- Autenticação JWT para atendentes
- Resposta em < 2 segundos para consultas
- Registro imutável de transações
- Suporte a 50 usuários concorrentes
#### Papéis e Permissões:
- **admin**: Todas as operações
- **attendant**: Ler, escrever, adicionar/consumir grãos
- **customer**: Apenas consultar próprio saldo
#### Como Executar:
```bash
cd /workspace
go run examples/protoagent/example_cafeteria.go
```
#### Artefatos Gerados:
- `output/cafeteria/AGENT.json` - Configuração do agente
- `output/cafeteria/schema_*.json` - Schemas de banco de dados
- `output/cafeteria/schema_*.sql` - DDL SQL
- `output/cafeteria/policy_*.rego` - Políticas OPA
- `output/cafeteria/channels.json` - Configuração Telegram
- `output/cafeteria/validation_report.json` - Relatório de validação
---
### b) Plataforma de Experiências de Viagem
**Arquivo:** `travel-experience-platform.json`
Plataforma onde viajantes compartilham relatos com mídias. O sistema atua como editor, filtrando dados pessoais, enriquecendo conteúdo e publicando em redes sociais.
#### Características Principais:
- **Submissão de Relatos**: Viajantes enviam histórias com fotos/vídeos
- **Filtragem de PII**: Detecção automática de dados pessoais
- **Enriquecimento com IA**: Solicita informações adicionais aos autores
- **Geração de Artigos**: Cria conteúdo formatado para redes sociais
- **Curadoria**: Aprovação, rejeição ou solicitação de atualizações
- **Publicação Automática**: Integração com APIs de redes sociais
- **Monitoramento de Engajamento**: Analytics das publicações
#### Requisitos Funcionais:
1. `SubmitTravelStory` - Submeter relato com mídias
2. `FilterPersonalData` - Filtrar informações pessoais (PII)
3. `EnrichStoryContent` - Coletar informações adicionais
4. `GenerateSocialMediaArticle` - Criar artigos para redes sociais
5. `CurateContent` - Curadoria (aprovar/rejeitar/atualizar)
6. `PublishToSocialMedia` - Publicar em Instagram, Facebook, etc.
7. `MonitorEngagement` - Acompanhar likes, shares, comments
8. `ManageTravelerProfile` - Perfis de viajantes
9. `ExternalAPIIntegration` - Integrar com APIs externas (clima, mapas)
10. `UpdateStory` - Atualizar relatos existentes
#### Requisitos Não-Funcionais:
- **Privacidade**: Detecção e remoção automática de PII (GDPR compliant)
- **Moderação**: Conteúdo revisado antes de publicação
- **Performance**: Processamento em até 30 segundos
- **Persistência**: Backup diário, histórico de versões (5 anos)
- **Escalabilidade**: Armazenamento cloud com CDN
- **Disponibilidade**: Fallback para APIs indisponíveis
#### Papéis e Permissões:
- **admin**: Todas as operações
- **curator**: Aprovar, rejeitar, publicar conteúdo
- **traveler**: Criar/editar próprios relatos
- **viewer**: Apenas leitura
#### Recursos de IA:
- Detecção de PII (informações pessoais identificáveis)
- Enriquecimento de conteúdo
- Geração automática de artigos
- Assistência na curadoria
#### Como Executar:
```bash
cd /workspace
go run examples/protoagent/example_travel.go
```
#### Artefatos Gerados:
- `output/travel/AGENT.json` + `AGENT.md` - Configuração do agente
- `output/travel/schema_*.json` + `.sql` - Schemas de banco de dados
- `output/travel/interfaces.json` - Definições de API e UI
- `output/travel/policy_*.rego` - Políticas OPA (RBAC, PII, moderação)
- `output/travel/channels.json` - Canais de comunicação
- `output/travel/skills.json` + `.go` - Skills personalizadas
- `output/travel/tools.json` - Ferramentas de integração
- `output/travel/mcp_config.json` - Configuração MCP
- `output/travel/validation_report.json` - Relatório de validação
---
## 🔧 Estrutura dos Arquivos de Requisitos
Os arquivos JSON seguem esta estrutura:
```json
{
"version": "1.0.0",
"name": "Nome do Sistema",
"description": "Descrição detalhada",
"functionalRequirements": [
{
"id": "FR001",
"type": "action|operation|resource|actor",
"name": "NomeDaOperacao",
"description": "Descrição do que faz",
"inputs": [...],
"outputs": [...],
"preconditions": [...],
"postconditions": [...],
"interactionMethods": ["api", "ui", "messaging", "webhook", "mcp"]
}
],
"nonFunctionalRequirements": [
{
"id": "NFR001",
"category": "security|performance|reliability|scalability",
"name": "NomeRequisito",
"description": "Descrição",
"constraints": {...},
"metrics": [...]
}
],
"securityRequirements": [
{
"roles": ["admin", "user"],
"permissions": ["read", "write", "delete"],
"authorizations": ["role_based"],
"securityControls": ["authentication", "authorization"],
"dataClassification": "internal"
}
],
"performanceRequirements": [...],
"metadata": {...}
}
```
## 🚀 Métodos de Interação Suportados
- `api` - Integração via API REST/GraphQL
- `mcp` - Model Context Protocol
- `ui` - Interface de usuário (web/cli)
- `messaging` - Aplicativos de mensagem (Telegram, Discord, Slack)
- `webhook` - Webhooks para integrações
- `cli` - Interface de linha de comando
- `database` - Acesso direto ao banco de dados
- `file` - Operações com arquivos
- `eventbus` - Barramento de eventos
## 📊 Tipos de Artefatos Gerados
### 1. Configuração de Agente (AGENT.md)
Define o comportamento, tools, skills e instruções do agente.
### 2. Schemas de Banco de Dados
- **SQL**: Tabelas com colunas, tipos, chaves primárias, índices
- **NoSQL**: Collections com schemas JSON
- **Migrações**: Scripts de criação/atualização
### 3. Interfaces
- **API**: Endpoints REST com métodos, paths, autenticação
- **Web UI**: Telas, rotas, componentes, ações
### 4. Canais de Comunicação
- **Telegram**: Bot com comandos e handlers
- **Discord/Slack**: Integrações similares
- **Webhooks**: Endpoints para recebimento de eventos
### 5. Políticas OPA (Open Policy Agent)
- **RBAC**: Controle de acesso baseado em papéis
- **Autorização**: Políticas customizadas
- **Acesso a Dados**: Classificação e controle de sensibilidade
### 6. Skills
Código Go personalizado para operações específicas do domínio.
### 7. Tools
Configurações para ferramentas de integração (HTTP, database, filesystem).
### 8. Configuração MCP
Servidores Model Context Protocol para contexto adicional.
## 🛠️ Workflow de Desenvolvimento
1. **Definir Requisitos**: Crie um arquivo JSON descrevendo funcionalidades e restrições
2. **Executar ProtoAgent**: Rode o exemplo correspondente
3. **Revisar Artefatos**: Analise os arquivos gerados em `output/`
4. **Customizar**: Ajuste conforme necessidades específicas
5. **Implantar**: Copie artefatos para o workspace do PicoClaw
## 📝 Próximos Passos Sugeridos
Após gerar os artefatos:
1. **Configurar Credenciais**:
- Token do Telegram em `channels.json`
- URLs de APIs externas
- Connection string do banco de dados
2. **Implementar Skills**:
- Complete o código das skills geradas
- Adicione lógica de negócio específica
3. **Testar Políticas OPA**:
```bash
opa eval -i input.json -d policy.rego "authz.rbac.allow"
```
4. **Integrar com PicoClaw**:
- Copie `AGENT.md` para `workspace/`
- Coloque skills em `workspace/skills/`
- Configure channels no config do PicoClaw
## 📞 Suporte
Para mais informações, consulte:
- `pkg/protoagent/README.md` - Documentação completa do ProtoAgent
- `docs/configuration.md` - Configuração do PicoClaw
- `examples/` - Outros exemplos de uso

View file

@ -0,0 +1,197 @@
{
"version": "1.0.0",
"name": "Cafeteria Loyalty System",
"description": "Sistema de fidelização de cafeteria onde clientes acumulam 'grãos' baseados no consumo e podem trocar por produtos. Atendentes gerenciam contas de clientes e transações de grãos.",
"functionalRequirements": [
{
"id": "FR001",
"type": "resource",
"name": "ManageCustomer",
"description": "Gerenciar cadastro de clientes do programa de fidelidade",
"inputs": [
{"name": "customer_id", "type": "string", "required": true, "description": "Identificador único do cliente"},
{"name": "name", "type": "string", "required": true, "description": "Nome do cliente"},
{"name": "phone", "type": "string", "required": true, "description": "Telefone/WhatsApp para Telegram"},
{"name": "email", "type": "string", "required": false, "description": "Email do cliente"}
],
"outputs": [
{"name": "customer", "type": "object", "description": "Dados completos do cliente"}
],
"interactionMethods": ["api", "ui", "messaging"]
},
{
"id": "FR002",
"type": "action",
"name": "AddGrains",
"description": "Adicionar grãos à conta do cliente baseado na compra",
"inputs": [
{"name": "customer_id", "type": "string", "required": true, "description": "ID do cliente"},
{"name": "amount", "type": "integer", "required": true, "description": "Quantidade de grãos a adicionar"},
{"name": "purchase_value", "type": "decimal", "required": true, "description": "Valor da compra em reais"},
{"name": "attendant_id", "type": "string", "required": true, "description": "ID do atendente realizando a operação"}
],
"outputs": [
{"name": "new_balance", "type": "integer", "description": "Novo saldo de grãos"},
{"name": "transaction_id", "type": "string", "description": "ID da transação"}
],
"preconditions": ["Cliente deve estar cadastrado", "Atendente deve estar autenticado"],
"postconditions": ["Saldo atualizado", "Transação registrada"],
"interactionMethods": ["api", "ui"]
},
{
"id": "FR003",
"type": "action",
"name": "RedeemGrains",
"description": "Consumir grãos para resgatar produtos",
"inputs": [
{"name": "customer_id", "type": "string", "required": true, "description": "ID do cliente"},
{"name": "product_id", "type": "string", "required": true, "description": "ID do produto a resgatar"},
{"name": "grains_required", "type": "integer", "required": true, "description": "Quantidade de grãos necessários"},
{"name": "attendant_id", "type": "string", "required": true, "description": "ID do atendente"}
],
"outputs": [
{"name": "success", "type": "boolean", "description": "Se o resgate foi bem sucedido"},
{"name": "remaining_balance", "type": "integer", "description": "Saldo restante após resgate"}
],
"preconditions": ["Cliente deve ter saldo suficiente", "Produto deve estar disponível"],
"postconditions": ["Saldo deduzido", "Resgate registrado", "Produto marcado como entregue"],
"interactionMethods": ["api", "ui"]
},
{
"id": "FR004",
"type": "operation",
"name": "ConsultBalance",
"description": "Consultar saldo de grãos do cliente",
"inputs": [
{"name": "customer_id", "type": "string", "required": true, "description": "ID do cliente"}
],
"outputs": [
{"name": "balance", "type": "integer", "description": "Saldo atual de grãos"},
{"name": "last_updated", "type": "timestamp", "description": "Data da última atualização"}
],
"interactionMethods": ["api", "ui", "messaging"]
},
{
"id": "FR005",
"type": "resource",
"name": "ManageProducts",
"description": "Gerenciar catálogo de produtos disponíveis para resgate",
"inputs": [
{"name": "product_id", "type": "string", "required": false, "description": "ID do produto"},
{"name": "name", "type": "string", "required": true, "description": "Nome do produto"},
{"name": "grains_cost", "type": "integer", "required": true, "description": "Custo em grãos"},
{"name": "quantity", "type": "integer", "required": true, "description": "Quantidade disponível"},
{"name": "category", "type": "string", "required": false, "description": "Categoria do produto"}
],
"outputs": [
{"name": "product", "type": "object", "description": "Dados do produto"}
],
"interactionMethods": ["api", "ui"]
},
{
"id": "FR006",
"type": "operation",
"name": "TelegramBalanceQuery",
"description": "Permitir que clientes consultem saldo via Telegram",
"inputs": [
{"name": "telegram_user_id", "type": "string", "required": true, "description": "ID do usuário no Telegram"}
],
"outputs": [
{"name": "balance", "type": "integer", "description": "Saldo de grãos"},
{"name": "message", "type": "string", "description": "Mensagem formatada para Telegram"}
],
"interactionMethods": ["messaging"]
},
{
"id": "FR007",
"type": "operation",
"name": "TransactionHistory",
"description": "Listar histórico de transações de grãos",
"inputs": [
{"name": "customer_id", "type": "string", "required": true, "description": "ID do cliente"},
{"name": "limit", "type": "integer", "required": false, "description": "Número máximo de registros", "default": "10"}
],
"outputs": [
{"name": "transactions", "type": "array", "description": "Lista de transações"}
],
"interactionMethods": ["api", "ui", "messaging"]
}
],
"nonFunctionalRequirements": [
{
"id": "NFR001",
"category": "security",
"name": "Authentication",
"description": "Todos os atendentes devem ser autenticados antes de realizar operações",
"constraints": {
"auth_type": "jwt",
"token_expiry": "8h",
"mfa_required": "false"
}
},
{
"id": "NFR002",
"category": "performance",
"name": "ResponseTime",
"description": "Operações de consulta de saldo devem responder em menos de 2 segundos",
"constraints": {
"max_response_time": "2s",
"percentile": "95"
},
"metrics": [
{"name": "avg_response_time", "target": "< 1s", "unit": "seconds"}
]
},
{
"id": "NFR003",
"category": "reliability",
"name": "DataIntegrity",
"description": "Todas as transações de grãos devem ser registradas de forma imutável",
"constraints": {
"audit_log": "true",
"transaction_log": "true"
}
},
{
"id": "NFR004",
"category": "scalability",
"name": "Concurrency",
"description": "Sistema deve suportar múltiplos atendentes operando simultaneamente",
"constraints": {
"max_concurrent_users": "50"
}
}
],
"securityRequirements": [
{
"roles": ["admin", "attendant", "customer"],
"permissions": ["read", "write", "delete", "redeem", "add_grains"],
"authorizations": ["role_based", "resource_owner"],
"securityControls": ["authentication", "authorization", "audit_logging"],
"dataClassification": "internal"
}
],
"performanceRequirements": [
{
"responseTime": "2s",
"throughput": 100,
"concurrency": 50,
"resourceLimits": {
"memory": "512MB",
"cpu": "1 core",
"storage": "10GB"
}
}
],
"metadata": {
"domain": "retail",
"industry": "food_beverage",
"channel_priority": ["telegram", "ui", "api"],
"database_type": "sql"
}
}

View file

@ -0,0 +1,224 @@
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/sipeed/picoclaw/pkg/protoagent"
)
// Este exemplo demonstra como usar o ProtoAgent para gerar artefatos
// a partir de requisitos de um sistema de fidelização de cafeteria
func main() {
// Carregar requisitos do arquivo JSON
reqs, err := loadRequirements("examples/protoagent/cafeteria-loyalty-system.json")
if err != nil {
fmt.Printf("Erro ao carregar requisitos: %v\n", err)
os.Exit(1)
}
// Configurar o engine do ProtoAgent
config := protoagent.EngineConfig{
OutputDir: "./output/cafeteria",
Workspace: "./workspace",
EnableOPA: true,
EnableAI: false,
DryRun: false,
Verbose: true,
}
engine := protoagent.NewEngine(config)
// Processar requisitos e gerar artefatos
ctx := context.Background()
artifacts, err := engine.ProcessRequirements(ctx, reqs)
if err != nil {
fmt.Printf("Erro ao processar requisitos: %v\n", err)
os.Exit(1)
}
// Exibir resumo dos artefatos gerados
printArtifactsSummary(artifacts)
// Salvar artefatos em arquivos
if err := saveArtifacts(artifacts); err != nil {
fmt.Printf("Erro ao salvar artefatos: %v\n", err)
os.Exit(1)
}
fmt.Println("\n✅ Artefatos gerados com sucesso!")
}
// loadRequirements carrega requisitos de um arquivo JSON
func loadRequirements(filename string) (*protoagent.RequirementsDocument, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("falha ao ler arquivo: %w", err)
}
var reqs protoagent.RequirementsDocument
if err := json.Unmarshal(data, &reqs); err != nil {
return nil, fmt.Errorf("falha ao parsear JSON: %w", err)
}
return &reqs, nil
}
// printArtifactsSummary exibe um resumo dos artefatos gerados
func printArtifactsSummary(artifacts *protoagent.GeneratedArtifacts) {
fmt.Println("\n📦 Resumo dos Artefatos Gerados:")
fmt.Println("=" + string(make([]byte, 50)))
if artifacts.AgentConfig != nil {
fmt.Printf("🤖 Agente: %s\n", artifacts.AgentConfig.Name)
fmt.Printf(" Descrição: %s\n", artifacts.AgentConfig.Description)
fmt.Printf(" Tools: %v\n", artifacts.AgentConfig.Tools)
}
if len(artifacts.DatabaseSchemas) > 0 {
fmt.Printf("\n💾 Schemas de Banco de Dados: %d\n", len(artifacts.DatabaseSchemas))
for _, schema := range artifacts.DatabaseSchemas {
fmt.Printf(" - %s (%s)\n", schema.Name, schema.Type)
if len(schema.Tables) > 0 {
for _, table := range schema.Tables {
fmt.Printf(" Tabela: %s (%d colunas)\n", table.Name, len(table.Columns))
}
}
}
}
if len(artifacts.Interfaces) > 0 {
fmt.Printf("\n🖥 Interfaces: %d\n", len(artifacts.Interfaces))
for _, iface := range artifacts.Interfaces {
fmt.Printf(" - %s (%s)\n", iface.Name, iface.Type)
if iface.Type == "api" && len(iface.Endpoints) > 0 {
fmt.Printf(" Endpoints: %d\n", len(iface.Endpoints))
}
if iface.Type == "web" && len(iface.Screens) > 0 {
fmt.Printf(" Telas: %d\n", len(iface.Screens))
}
}
}
if len(artifacts.Channels) > 0 {
fmt.Printf("\n📱 Canais de Comunicação: %d\n", len(artifacts.Channels))
for _, channel := range artifacts.Channels {
fmt.Printf(" - %s (%s) - Habilitado: %v\n", channel.Name, channel.Type, channel.Enabled)
if len(channel.Commands) > 0 {
fmt.Printf(" Comandos: %d\n", len(channel.Commands))
}
}
}
if len(artifacts.Policies) > 0 {
fmt.Printf("\n🔐 Políticas OPA: %d\n", len(artifacts.Policies))
for _, policy := range artifacts.Policies {
fmt.Printf(" - %s (%s)\n", policy.Name, policy.Package)
fmt.Printf(" Descrição: %s\n", policy.Description)
}
}
if len(artifacts.Skills) > 0 {
fmt.Printf("\n🎯 Skills: %d\n", len(artifacts.Skills))
for _, skill := range artifacts.Skills {
fmt.Printf(" - %s\n", skill.Name)
fmt.Printf(" Descrição: %s\n", skill.Description)
}
}
if len(artifacts.Tools) > 0 {
fmt.Printf("\n🔧 Tools: %d\n", len(artifacts.Tools))
for _, tool := range artifacts.Tools {
fmt.Printf(" - %s (%s)\n", tool.Name, tool.Type)
}
}
if artifacts.MCPConfig != nil && len(artifacts.MCPConfig.Servers) > 0 {
fmt.Printf("\n🔌 Servidores MCP: %d\n", len(artifacts.MCPConfig.Servers))
for _, server := range artifacts.MCPConfig.Servers {
fmt.Printf(" - %s (%s)\n", server.Name, server.Type)
}
}
if artifacts.ValidationReport != nil {
fmt.Printf("\n✅ Validação: %v\n", artifacts.ValidationReport.Valid)
if len(artifacts.ValidationReport.Errors) > 0 {
fmt.Printf(" Erros: %d\n", len(artifacts.ValidationReport.Errors))
}
if len(artifacts.ValidationReport.Warnings) > 0 {
fmt.Printf(" Alertas: %d\n", len(artifacts.ValidationReport.Warnings))
}
if len(artifacts.ValidationReport.Suggestions) > 0 {
fmt.Printf(" Sugestões: %d\n", len(artifacts.ValidationReport.Suggestions))
for _, sug := range artifacts.ValidationReport.Suggestions {
fmt.Printf(" 💡 %s\n", sug)
}
}
}
}
// saveArtifacts salva os artefatos em arquivos
func saveArtifacts(artifacts *protoagent.GeneratedArtifacts) error {
// Criar diretório de saída
if err := os.MkdirAll("./output/cafeteria", 0755); err != nil {
return fmt.Errorf("falha ao criar diretório: %w", err)
}
// Salvar AGENT.md
if artifacts.AgentConfig != nil {
agentJSON, _ := json.MarshalIndent(artifacts.AgentConfig, "", " ")
if err := os.WriteFile("./output/cafeteria/AGENT.json", agentJSON, 0644); err != nil {
return fmt.Errorf("falha ao salvar AGENT.json: %w", err)
}
fmt.Println("\n📄 AGENT.json salvo")
}
// Salvar schemas de banco de dados
for i, schema := range artifacts.DatabaseSchemas {
schemaJSON, _ := json.MarshalIndent(schema, "", " ")
filename := fmt.Sprintf("./output/cafeteria/schema_%d_%s.json", i, schema.Name)
if err := os.WriteFile(filename, schemaJSON, 0644); err != nil {
return fmt.Errorf("falha ao salvar schema: %w", err)
}
fmt.Printf("📄 Schema %s salvo\n", schema.Name)
}
// Salvar políticas OPA
for i, policy := range artifacts.Policies {
policyJSON, _ := json.MarshalIndent(policy, "", " ")
filename := fmt.Sprintf("./output/cafeteria/policy_%d_%s.rego.json", i, policy.Name)
if err := os.WriteFile(filename, policyJSON, 0644); err != nil {
return fmt.Errorf("falha ao salvar política: %w", err)
}
fmt.Printf("📄 Política %s salva\n", policy.Name)
// Salvar também o código Rego puro
regoFilename := fmt.Sprintf("./output/cafeteria/policy_%d_%s.rego", i, policy.Name)
if err := os.WriteFile(regoFilename, []byte(policy.Rego), 0644); err != nil {
return fmt.Errorf("falha ao salvar rego: %w", err)
}
fmt.Printf("📄 Código Rego %s salvo\n", policy.Name)
}
// Salvar channels
if len(artifacts.Channels) > 0 {
channelsJSON, _ := json.MarshalIndent(artifacts.Channels, "", " ")
if err := os.WriteFile("./output/cafeteria/channels.json", channelsJSON, 0644); err != nil {
return fmt.Errorf("falha ao salvar channels: %w", err)
}
fmt.Println("📄 channels.json salvo")
}
// Salvar relatório de validação
if artifacts.ValidationReport != nil {
reportJSON, _ := json.MarshalIndent(artifacts.ValidationReport, "", " ")
if err := os.WriteFile("./output/cafeteria/validation_report.json", reportJSON, 0644); err != nil {
return fmt.Errorf("falha ao salvar validation report: %w", err)
}
fmt.Println("📄 validation_report.json salvo")
}
return nil
}

View file

@ -0,0 +1,419 @@
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/sipeed/picoclaw/pkg/protoagent"
)
// Este exemplo demonstra como usar o ProtoAgent para gerar artefatos
// a partir de requisitos de uma plataforma de experiências de viagem
func main() {
// Carregar requisitos do arquivo JSON
reqs, err := loadRequirements("examples/protoagent/travel-experience-platform.json")
if err != nil {
fmt.Printf("Erro ao carregar requisitos: %v\n", err)
os.Exit(1)
}
// Configurar o engine do ProtoAgent
config := protoagent.EngineConfig{
OutputDir: "./output/travel",
Workspace: "./workspace",
EnableOPA: true,
EnableAI: true, // Habilitar IA para recursos de curadoria e enriquecimento
DryRun: false,
Verbose: true,
}
engine := protoagent.NewEngine(config)
// Processar requisitos e gerar artefatos
ctx := context.Background()
artifacts, err := engine.ProcessRequirements(ctx, reqs)
if err != nil {
fmt.Printf("Erro ao processar requisitos: %v\n", err)
os.Exit(1)
}
// Exibir resumo dos artefatos gerados
printArtifactsSummary(artifacts)
// Salvar artefatos em arquivos
if err := saveArtifacts(artifacts); err != nil {
fmt.Printf("Erro ao salvar artefatos: %v\n", err)
os.Exit(1)
}
fmt.Println("\n✅ Artefatos gerados com sucesso!")
}
// loadRequirements carrega requisitos de um arquivo JSON
func loadRequirements(filename string) (*protoagent.RequirementsDocument, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("falha ao ler arquivo: %w", err)
}
var reqs protoagent.RequirementsDocument
if err := json.Unmarshal(data, &reqs); err != nil {
return nil, fmt.Errorf("falha ao parsear JSON: %w", err)
}
return &reqs, nil
}
// printArtifactsSummary exibe um resumo dos artefatos gerados
func printArtifactsSummary(artifacts *protoagent.GeneratedArtifacts) {
fmt.Println("\n📦 Resumo dos Artefatos Gerados:")
fmt.Println("=" + string(make([]byte, 50)))
if artifacts.AgentConfig != nil {
fmt.Printf("🤖 Agente: %s\n", artifacts.AgentConfig.Name)
fmt.Printf(" Descrição: %s\n", artifacts.AgentConfig.Description)
fmt.Printf(" Tools: %v\n", artifacts.AgentConfig.Tools)
fmt.Printf(" Skills: %v\n", artifacts.AgentConfig.Skills)
}
if len(artifacts.DatabaseSchemas) > 0 {
fmt.Printf("\n💾 Schemas de Banco de Dados: %d\n", len(artifacts.DatabaseSchemas))
for _, schema := range artifacts.DatabaseSchemas {
fmt.Printf(" - %s (%s)\n", schema.Name, schema.Type)
if len(schema.Tables) > 0 {
for _, table := range schema.Tables {
fmt.Printf(" Tabela: %s (%d colunas)\n", table.Name, len(table.Columns))
for _, col := range table.Columns {
fmt.Printf(" - %s: %s", col.Name, col.Type)
if col.PrimaryKey {
fmt.Print(" [PK]")
}
if !col.Nullable {
fmt.Print(" [NOT NULL]")
}
fmt.Println()
}
}
}
}
}
if len(artifacts.Interfaces) > 0 {
fmt.Printf("\n🖥 Interfaces: %d\n", len(artifacts.Interfaces))
for _, iface := range artifacts.Interfaces {
fmt.Printf(" - %s (%s)\n", iface.Name, iface.Type)
if iface.Type == "api" && len(iface.Endpoints) > 0 {
fmt.Printf(" Endpoints: %d\n", len(iface.Endpoints))
for _, ep := range iface.Endpoints {
fmt.Printf(" [%s] %s - %s\n", ep.Method, ep.Path, ep.Description)
}
}
if iface.Type == "web" && len(iface.Screens) > 0 {
fmt.Printf(" Telas: %d\n", len(iface.Screens))
for _, screen := range iface.Screens {
fmt.Printf(" 📱 %s (%s)\n", screen.Name, screen.Route)
if len(screen.Components) > 0 {
fmt.Printf(" Componentes: %d\n", len(screen.Components))
}
}
}
}
}
if len(artifacts.Channels) > 0 {
fmt.Printf("\n📱 Canais de Comunicação: %d\n", len(artifacts.Channels))
for _, channel := range artifacts.Channels {
fmt.Printf(" - %s (%s) - Habilitado: %v\n", channel.Name, channel.Type, channel.Enabled)
if len(channel.Config) > 0 {
fmt.Printf(" Configuração:\n")
for k, v := range channel.Config {
fmt.Printf(" %s: %s\n", k, v)
}
}
}
}
if len(artifacts.Policies) > 0 {
fmt.Printf("\n🔐 Políticas OPA: %d\n", len(artifacts.Policies))
for _, policy := range artifacts.Policies {
fmt.Printf(" - %s (%s)\n", policy.Name, policy.Package)
fmt.Printf(" Descrição: %s\n", policy.Description)
// Mostrar preview do código Rego
regoLines := splitLines(policy.Rego)
if len(regoLines) > 0 {
fmt.Printf(" Código Rego (%d linhas):\n", len(regoLines))
previewLen := len(regoLines)
if previewLen > 5 {
previewLen = 5
}
for i := 0; i < previewLen; i++ {
fmt.Printf(" %s\n", regoLines[i])
}
if len(regoLines) > 5 {
fmt.Printf(" ... (%d linhas restantes)\n", len(regoLines)-5)
}
}
}
}
if len(artifacts.Skills) > 0 {
fmt.Printf("\n🎯 Skills: %d\n", len(artifacts.Skills))
for _, skill := range artifacts.Skills {
fmt.Printf(" - %s\n", skill.Name)
fmt.Printf(" Descrição: %s\n", skill.Description)
if len(skill.Triggers) > 0 {
fmt.Printf(" Triggers: %v\n", skill.Triggers)
}
if len(skill.Dependencies) > 0 {
fmt.Printf(" Dependências: %v\n", skill.Dependencies)
}
}
}
if len(artifacts.Tools) > 0 {
fmt.Printf("\n🔧 Tools: %d\n", len(artifacts.Tools))
for _, tool := range artifacts.Tools {
fmt.Printf(" - %s (%s)\n", tool.Name, tool.Type)
fmt.Printf(" Descrição: %s\n", tool.Description)
if len(tool.Config) > 0 {
fmt.Printf(" Configuração:\n")
for k, v := range tool.Config {
fmt.Printf(" %s: %s\n", k, v)
}
}
}
}
if artifacts.MCPConfig != nil && len(artifacts.MCPConfig.Servers) > 0 {
fmt.Printf("\n🔌 Servidores MCP: %d\n", len(artifacts.MCPConfig.Servers))
for _, server := range artifacts.MCPConfig.Servers {
fmt.Printf(" - %s (%s)\n", server.Name, server.Type)
if server.Command != "" {
fmt.Printf(" Comando: %s %v\n", server.Command, server.Args)
}
if server.URL != "" {
fmt.Printf(" URL: %s\n", server.URL)
}
}
}
if artifacts.ValidationReport != nil {
fmt.Printf("\n✅ Validação: %v\n", artifacts.ValidationReport.Valid)
if len(artifacts.ValidationReport.Errors) > 0 {
fmt.Printf(" ❌ Erros: %d\n", len(artifacts.ValidationReport.Errors))
for _, err := range artifacts.ValidationReport.Errors {
fmt.Printf(" - %s: %s\n", err.Field, err.Message)
}
}
if len(artifacts.ValidationReport.Warnings) > 0 {
fmt.Printf(" ⚠️ Alertas: %d\n", len(artifacts.ValidationReport.Warnings))
for _, warn := range artifacts.ValidationReport.Warnings {
fmt.Printf(" - %s: %s\n", warn.Field, warn.Message)
}
}
if len(artifacts.ValidationReport.Suggestions) > 0 {
fmt.Printf(" 💡 Sugestões: %d\n", len(artifacts.ValidationReport.Suggestions))
for _, sug := range artifacts.ValidationReport.Suggestions {
fmt.Printf(" - %s\n", sug)
}
}
}
}
// splitLines divide uma string em linhas
func splitLines(s string) []string {
var lines []string
current := ""
for _, c := range s {
if c == '\n' {
lines = append(lines, current)
current = ""
} else {
current += string(c)
}
}
if current != "" {
lines = append(lines, current)
}
return lines
}
// saveArtifacts salva os artefatos em arquivos
func saveArtifacts(artifacts *protoagent.GeneratedArtifacts) error {
// Criar diretório de saída
if err := os.MkdirAll("./output/travel", 0755); err != nil {
return fmt.Errorf("falha ao criar diretório: %w", err)
}
// Salvar AGENT.md
if artifacts.AgentConfig != nil {
agentJSON, _ := json.MarshalIndent(artifacts.AgentConfig, "", " ")
if err := os.WriteFile("./output/travel/AGENT.json", agentJSON, 0644); err != nil {
return fmt.Errorf("falha ao salvar AGENT.json: %w", err)
}
// Também salvar como Markdown
agentMD := fmt.Sprintf("# %s Agent\n\n%s\n", artifacts.AgentConfig.Name, artifacts.AgentConfig.Body)
if err := os.WriteFile("./output/travel/AGENT.md", []byte(agentMD), 0644); err != nil {
return fmt.Errorf("falha ao salvar AGENT.md: %w", err)
}
fmt.Println("\n📄 AGENT.json e AGENT.md salvos")
}
// Salvar schemas de banco de dados
for i, schema := range artifacts.DatabaseSchemas {
schemaJSON, _ := json.MarshalIndent(schema, "", " ")
filename := fmt.Sprintf("./output/travel/schema_%d_%s.json", i, schema.Name)
if err := os.WriteFile(filename, schemaJSON, 0644); err != nil {
return fmt.Errorf("falha ao salvar schema: %w", err)
}
// Gerar SQL DDL
if schema.Type == "sql" && len(schema.Tables) > 0 {
sqlDDL := generateSQLDDL(schema)
sqlFilename := fmt.Sprintf("./output/travel/schema_%d_%s.sql", i, schema.Name)
if err := os.WriteFile(sqlFilename, []byte(sqlDDL), 0644); err != nil {
return fmt.Errorf("falha ao salvar SQL: %w", err)
}
fmt.Printf("📄 Schema %s salvo (JSON + SQL)\n", schema.Name)
} else {
fmt.Printf("📄 Schema %s salvo\n", schema.Name)
}
}
// Salvar políticas OPA
for i, policy := range artifacts.Policies {
policyJSON, _ := json.MarshalIndent(policy, "", " ")
filename := fmt.Sprintf("./output/travel/policy_%d_%s.rego.json", i, policy.Name)
if err := os.WriteFile(filename, policyJSON, 0644); err != nil {
return fmt.Errorf("falha ao salvar política: %w", err)
}
fmt.Printf("📄 Política %s salva (JSON)\n", policy.Name)
// Salvar também o código Rego puro
regoFilename := fmt.Sprintf("./output/travel/policy_%d_%s.rego", i, policy.Name)
if err := os.WriteFile(regoFilename, []byte(policy.Rego), 0644); err != nil {
return fmt.Errorf("falha ao salvar rego: %w", err)
}
fmt.Printf("📄 Código Rego %s salvo\n", policy.Name)
}
// Salvar interfaces
if len(artifacts.Interfaces) > 0 {
interfacesJSON, _ := json.MarshalIndent(artifacts.Interfaces, "", " ")
if err := os.WriteFile("./output/travel/interfaces.json", interfacesJSON, 0644); err != nil {
return fmt.Errorf("falha ao salvar interfaces: %w", err)
}
fmt.Println("📄 interfaces.json salvo")
}
// Salvar channels
if len(artifacts.Channels) > 0 {
channelsJSON, _ := json.MarshalIndent(artifacts.Channels, "", " ")
if err := os.WriteFile("./output/travel/channels.json", channelsJSON, 0644); err != nil {
return fmt.Errorf("falha ao salvar channels: %w", err)
}
fmt.Println("📄 channels.json salvo")
}
// Salvar skills
if len(artifacts.Skills) > 0 {
skillsJSON, _ := json.MarshalIndent(artifacts.Skills, "", " ")
if err := os.WriteFile("./output/travel/skills.json", skillsJSON, 0644); err != nil {
return fmt.Errorf("falha ao salvar skills: %w", err)
}
fmt.Println("📄 skills.json salvo")
// Salvar código de cada skill
for i, skill := range artifacts.Skills {
skillFile := fmt.Sprintf("./output/travel/skill_%d_%s.go", i, skill.Name)
if err := os.WriteFile(skillFile, []byte(skill.Code), 0644); err != nil {
return fmt.Errorf("falha ao salvar código da skill: %w", err)
}
}
fmt.Println("📄 Códigos das skills salvos")
}
// Salvar tools
if len(artifacts.Tools) > 0 {
toolsJSON, _ := json.MarshalIndent(artifacts.Tools, "", " ")
if err := os.WriteFile("./output/travel/tools.json", toolsJSON, 0644); err != nil {
return fmt.Errorf("falha ao salvar tools: %w", err)
}
fmt.Println("📄 tools.json salvo")
}
// Salvar configuração MCP
if artifacts.MCPConfig != nil {
mcpJSON, _ := json.MarshalIndent(artifacts.MCPConfig, "", " ")
if err := os.WriteFile("./output/travel/mcp_config.json", mcpJSON, 0644); err != nil {
return fmt.Errorf("falha ao salvar mcp_config: %w", err)
}
fmt.Println("📄 mcp_config.json salvo")
}
// Salvar relatório de validação
if artifacts.ValidationReport != nil {
reportJSON, _ := json.MarshalIndent(artifacts.ValidationReport, "", " ")
if err := os.WriteFile("./output/travel/validation_report.json", reportJSON, 0644); err != nil {
return fmt.Errorf("falha ao salvar validation report: %w", err)
}
fmt.Println("📄 validation_report.json salvo")
}
return nil
}
// generateSQLDDL gera DDL SQL a partir de um schema
func generateSQLDDL(schema protoagent.DatabaseSchema) string {
var ddl string
ddl += fmt.Sprintf("-- Schema: %s\n", schema.Name)
ddl += fmt.Sprintf("-- Type: %s\n\n", schema.Type)
for _, table := range schema.Tables {
ddl += fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (\n", table.Name)
columns := make([]string, 0, len(table.Columns))
for _, col := range table.Columns {
colDef := fmt.Sprintf(" %s %s", col.Name, col.Type)
if col.PrimaryKey {
colDef += " PRIMARY KEY"
}
if !col.Nullable {
colDef += " NOT NULL"
}
if col.Unique {
colDef += " UNIQUE"
}
if col.Default != "" {
colDef += fmt.Sprintf(" DEFAULT %s", col.Default)
}
columns = append(columns, colDef)
}
ddl += joinStrings(columns, ",\n")
ddl += "\n);\n\n"
// Criar índices
for _, idx := range table.Indexes {
ddl += fmt.Sprintf("CREATE INDEX ON %s (%s);\n", table.Name, idx)
}
}
return ddl
}
// joinStrings junta strings com um separador
func joinStrings(strs []string, sep string) string {
if len(strs) == 0 {
return ""
}
result := strs[0]
for i := 1; i < len(strs); i++ {
result += sep + strs[i]
}
return result
}

View file

@ -0,0 +1,291 @@
{
"version": "1.0.0",
"name": "Travel Experience Platform",
"description": "Plataforma de experiências de viagem onde viajantes compartilham relatos com mídias. O sistema atua como editor, filtrando dados pessoais, enriquecendo relatos com informações adicionais e publicando conteúdo curado em redes sociais.",
"functionalRequirements": [
{
"id": "FR001",
"type": "resource",
"name": "SubmitTravelStory",
"description": "Permitir que viajantes submetam relatos de viagens com mídias e conteúdos",
"inputs": [
{"name": "traveler_id", "type": "string", "required": true, "description": "ID do viajante"},
{"name": "title", "type": "string", "required": true, "description": "Título do relato"},
{"name": "content", "type": "text", "required": true, "description": "Conteúdo do relato"},
{"name": "location", "type": "object", "required": false, "description": "Localização da experiência"},
{"name": "media_files", "type": "array", "required": false, "description": "Fotos e vídeos anexados"},
{"name": "tags", "type": "array", "required": false, "description": "Tags para categorização"},
{"name": "travel_date", "type": "date", "required": false, "description": "Data da viagem"}
],
"outputs": [
{"name": "story_id", "type": "string", "description": "ID único do relato"},
{"name": "status", "type": "string", "description": "Status inicial (pending_review)"}
],
"preconditions": ["Viajante deve estar autenticado"],
"postconditions": ["Relato criado", "Status definido como pending_review"],
"interactionMethods": ["api", "ui", "webhook"]
},
{
"id": "FR002",
"type": "operation",
"name": "FilterPersonalData",
"description": "Filtrar automaticamente informações pessoais dos relatos para privacidade",
"inputs": [
{"name": "story_content", "type": "text", "required": true, "description": "Conteúdo original do relato"},
{"name": "media_files", "type": "array", "required": false, "description": "Mídias para análise"}
],
"outputs": [
{"name": "filtered_content", "type": "text", "description": "Conteúdo com dados pessoais removidos"},
{"name": "detected_pii", "type": "array", "description": "Lista de informações pessoais detectadas"},
{"name": "confidence_score", "type": "float", "description": "Confiança da detecção"}
],
"preconditions": ["Relato deve estar em revisão"],
"postconditions": ["Dados pessoais identificados", "Conteúdo filtrado gerado"],
"interactionMethods": ["api"]
},
{
"id": "FR003",
"type": "operation",
"name": "EnrichStoryContent",
"description": "Coletar informações adicionais do autor para enriquecer o relato",
"inputs": [
{"name": "story_id", "type": "string", "required": true, "description": "ID do relato"},
{"name": "missing_info", "type": "array", "required": true, "description": "Lista de informações faltantes"}
],
"outputs": [
{"name": "enriched_content", "type": "text", "description": "Conteúdo enriquecido"},
{"name": "additional_media", "type": "array", "description": "Mídias adicionais solicitadas"}
],
"preconditions": ["Relato deve estar em processo de curadoria"],
"postconditions": ["Informações adicionais coletadas", "Relato atualizado"],
"interactionMethods": ["messaging", "api"]
},
{
"id": "FR004",
"type": "operation",
"name": "GenerateSocialMediaArticle",
"description": "Elaborar artigos formatados para publicação em redes sociais",
"inputs": [
{"name": "story_id", "type": "string", "required": true, "description": "ID do relato"},
{"name": "platform", "type": "string", "required": true, "description": "Rede social destino (instagram, facebook, twitter, linkedin)"},
{"name": "tone", "type": "string", "required": false, "description": "Tom do conteúdo (inspirational, informative, adventurous)", "default": "inspirational"}
],
"outputs": [
{"name": "article_content", "type": "text", "description": "Artigo formatado para a plataforma"},
{"name": "hashtags", "type": "array", "description": "Hashtags sugeridas"},
{"name": "media_selection", "type": "array", "description": "Mídias selecionadas para publicação"}
],
"preconditions": ["Relato deve estar aprovado"],
"postconditions": ["Artigo gerado", "Pronto para publicação"],
"interactionMethods": ["api"]
},
{
"id": "FR005",
"type": "action",
"name": "CurateContent",
"description": "Realizar curadoria do conteúdo - aprovar, remover ou solicitar atualizações",
"inputs": [
{"name": "story_id", "type": "string", "required": true, "description": "ID do relato"},
{"name": "action", "type": "string", "required": true, "description": "Ação: approve, reject, request_update"},
{"name": "reason", "type": "string", "required": false, "description": "Motivo da decisão"},
{"name": "curator_id", "type": "string", "required": true, "description": "ID do curador/agente"}
],
"outputs": [
{"name": "new_status", "type": "string", "description": "Novo status do relato"},
{"name": "feedback", "type": "string", "description": "Feedback para o autor"}
],
"preconditions": ["Relato deve ter passado por filtragem de dados pessoais"],
"postconditions": ["Status atualizado", "Autor notificado se necessário"],
"interactionMethods": ["api", "ui"]
},
{
"id": "FR006",
"type": "action",
"name": "PublishToSocialMedia",
"description": "Publicar conteúdo aprovado em redes sociais via APIs externas",
"inputs": [
{"name": "article_id", "type": "string", "required": true, "description": "ID do artigo gerado"},
{"name": "platforms", "type": "array", "required": true, "description": "Lista de plataformas para publicação"},
{"name": "schedule_time", "type": "timestamp", "required": false, "description": "Agendamento da publicação"}
],
"outputs": [
{"name": "publication_ids", "type": "object", "description": "IDs das publicações em cada plataforma"},
{"name": "urls", "type": "array", "description": "URLs das publicações"}
],
"preconditions": ["Artigo deve estar aprovado", "APIs das redes sociais devem estar configuradas"],
"postconditions": ["Conteúdo publicado", "URLs registradas", "Engajamento monitorado"],
"interactionMethods": ["api", "webhook"]
},
{
"id": "FR007",
"type": "operation",
"name": "MonitorEngagement",
"description": "Monitorar engajamento das publicações nas redes sociais",
"inputs": [
{"name": "publication_id", "type": "string", "required": true, "description": "ID da publicação"},
{"name": "metrics", "type": "array", "required": false, "description": "Métricas para coletar (likes, shares, comments)", "default": ["likes", "shares", "comments"]}
],
"outputs": [
{"name": "engagement_data", "type": "object", "description": "Dados de engajamento coletados"},
{"name": "performance_score", "type": "float", "description": "Score de performance do conteúdo"}
],
"interactionMethods": ["api"]
},
{
"id": "FR008",
"type": "resource",
"name": "ManageTravelerProfile",
"description": "Gerenciar perfis de viajantes com histórico e preferências",
"inputs": [
{"name": "traveler_id", "type": "string", "required": true, "description": "ID do viajante"},
{"name": "bio", "type": "text", "required": false, "description": "Biografia do viajante"},
{"name": "preferences", "type": "object", "required": false, "description": "Preferências de privacidade e notificação"},
{"name": "social_links", "type": "array", "required": false, "description": "Links para redes sociais do viajante"}
],
"outputs": [
{"name": "profile", "type": "object", "description": "Perfil completo do viajante"}
],
"interactionMethods": ["api", "ui"]
},
{
"id": "FR009",
"type": "operation",
"name": "ExternalAPIIntegration",
"description": "Integrar com APIs externas para enriquecer relatos (clima, mapas, pontos turísticos)",
"inputs": [
{"name": "location", "type": "object", "required": true, "description": "Localização da viagem"},
{"name": "date_range", "type": "object", "required": false, "description": "Período da viagem"},
{"name": "api_sources", "type": "array", "required": false, "description": "Fontes de API para consultar"}
],
"outputs": [
{"name": "enrichment_data", "type": "object", "description": "Dados externos coletados"}
],
"interactionMethods": ["api"]
},
{
"id": "FR010",
"type": "action",
"name": "UpdateStory",
"description": "Atualizar relatos existentes com novas informações ou correções",
"inputs": [
{"name": "story_id", "type": "string", "required": true, "description": "ID do relato"},
{"name": "updates", "type": "object", "required": true, "description": "Campos a serem atualizados"},
{"name": "author_id", "type": "string", "required": true, "description": "ID do autor solicitando atualização"}
],
"outputs": [
{"name": "updated_story", "type": "object", "description": "Relato atualizado"},
{"name": "version", "type": "integer", "description": "Número da versão"}
],
"preconditions": ["Autor deve ser dono do relato", "Relato não pode estar bloqueado"],
"postconditions": ["Relato atualizado", "Histórico de versões mantido"],
"interactionMethods": ["api", "ui"]
}
],
"nonFunctionalRequirements": [
{
"id": "NFR001",
"category": "security",
"name": "PrivacyProtection",
"description": "Sistema deve detectar e remover automaticamente informações pessoais identificáveis (PII)",
"constraints": {
"pii_detection": "true",
"auto_redaction": "true",
"gdpr_compliance": "true"
}
},
{
"id": "NFR002",
"category": "security",
"name": "ContentModeration",
"description": "Conteúdo deve passar por moderação antes de publicação",
"constraints": {
"moderation_required": "true",
"ai_assisted": "true",
"human_review_threshold": "low_confidence"
}
},
{
"id": "NFR003",
"category": "performance",
"name": "ProcessingTime",
"description": "Processamento de filtragem e enriquecimento deve completar em até 30 segundos",
"constraints": {
"max_processing_time": "30s",
"async_processing": "true"
},
"metrics": [
{"name": "avg_filtering_time", "target": "< 10s", "unit": "seconds"},
{"name": "avg_enrichment_time", "target": "< 20s", "unit": "seconds"}
]
},
{
"id": "NFR004",
"category": "reliability",
"name": "DataPersistence",
"description": "Todos os relatos e versões devem ser persistidos com backup automático",
"constraints": {
"backup_frequency": "daily",
"version_history": "true",
"retention_period": "5 years"
}
},
{
"id": "NFR005",
"category": "scalability",
"name": "MediaStorage",
"description": "Sistema deve escalar armazenamento de mídias conforme demanda",
"constraints": {
"storage_type": "cloud_object_storage",
"cdn_enabled": "true",
"auto_scaling": "true"
}
},
{
"id": "NFR006",
"category": "availability",
"name": "APIAvailability",
"description": "APIs externas de redes sociais devem ter fallback em caso de indisponibilidade",
"constraints": {
"retry_policy": "exponential_backoff",
"fallback_queue": "true",
"max_retries": "5"
}
}
],
"securityRequirements": [
{
"roles": ["admin", "curator", "traveler", "viewer"],
"permissions": ["read", "write", "delete", "approve", "reject", "publish", "edit_own"],
"authorizations": ["role_based", "resource_owner", "content_moderation"],
"securityControls": ["authentication", "authorization", "pii_detection", "audit_logging", "content_moderation"],
"dataClassification": "mixed"
}
],
"performanceRequirements": [
{
"responseTime": "5s",
"throughput": 500,
"concurrency": 200,
"resourceLimits": {
"memory": "2GB",
"cpu": "4 cores",
"storage": "100GB",
"network": "1Gbps"
}
}
],
"metadata": {
"domain": "travel",
"industry": "tourism",
"channel_priority": ["api", "ui", "webhook", "messaging"],
"database_type": "nosql",
"media_storage": "cloud",
"external_integrations": ["instagram_api", "facebook_api", "twitter_api", "linkedin_api", "weather_api", "maps_api"],
"ai_features": ["pii_detection", "content_enrichment", "article_generation", "curation_assistance"]
}
}

6
go.mod
View file

@ -1,6 +1,6 @@
module github.com/sipeed/picoclaw
go 1.25.9
go 1.19
require (
fyne.io/systray v1.12.0
@ -26,8 +26,8 @@ require (
github.com/larksuite/oapi-sdk-go/v3 v3.5.3
github.com/mdp/qrterminal/v3 v3.2.1
github.com/minio/selfupdate v0.6.0
github.com/muesli/termenv v0.16.0
github.com/modelcontextprotocol/go-sdk v1.5.0
github.com/muesli/termenv v0.16.0
github.com/mymmrac/telego v1.8.0
github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1
github.com/openai/openai-go/v3 v3.22.0
@ -139,7 +139,7 @@ require (
golang.org/x/crypto v0.49.0
golang.org/x/net v0.52.0
golang.org/x/sync v0.20.0
golang.org/x/sys v0.43.0
golang.org/x/sys v0.15.0
)
replace github.com/bwmarrin/discordgo => github.com/yeongaori/discordgo-fork v0.0.0-20260319072544-e8e546f5d532

219
pkg/protoagent/README.md Normal file
View file

@ -0,0 +1,219 @@
# ProtoAgent - Behavior Prototyping Tool
ProtoAgent é uma ferramenta de prototipagem de comportamentos que transforma requisitos funcionais e não-funcionais em configurações de agentes, schemas de banco de dados, interfaces, canais de comunicação e políticas de segurança.
## Visão Geral
O ProtoAgent estende o PicoClaw para permitir que você descreva o comportamento desejado de um agente através de requisitos estruturados, e automaticamente gera:
- **Configurações de Agente** (AGENT.md)
- **Schemas de Banco de Dados** (SQL/NoSQL)
- **Interfaces** (API, Web UI)
- **Canais de Comunicação** (Telegram, Discord, Slack, Webhooks)
- **Políticas OPA** (Open Policy Agent para controle de acesso)
- **Skills** (Habilidades personalizadas)
- **Tools** (Ferramentas de integração)
- **Configuração MCP** (Model Context Protocol)
## Estrutura do Pacote
```
pkg/protoagent/
├── types.go # Definições de tipos e estruturas de dados
├── engine.go # Motor principal de processamento
├── generators.go # Geradores de artefatos
└── policies.go # Gerador de políticas OPA
```
## Tipos de Requisitos
### Requisitos Funcionais
Descrevem **o que** o sistema deve fazer:
```yaml
functionalRequirements:
- id: FR001
type: action
name: CreateUser
description: Create a new user account
inputs:
- name: username
type: string
required: true
- name: email
type: string
required: true
interactionMethods:
- api
- ui
```
### Requisitos Não-Funcionais
Descrevem **restrições e atributos de qualidade**:
```yaml
nonFunctionalRequirements:
- id: NFR001
category: security
name: Authentication
description: All API calls must be authenticated
constraints:
auth_type: jwt
token_expiry: 24h
```
## Métodos de Interação
- `api` - Integração via API REST/GraphQL
- `mcp` - Model Context Protocol
- `ui` - Interface de usuário (web/cli)
- `messaging` - Aplicativos de mensagem (Telegram, Discord, etc.)
- `webhook` - Webhooks para integrações
- `cli` - Interface de linha de comando
- `database` - Acesso direto ao banco de dados
- `file` - Operações com arquivos
- `eventbus` - Barramento de eventos
## Exemplo de Uso
```go
package main
import (
"context"
"github.com/sipeed/picoclaw/pkg/protoagent"
)
func main() {
// Configurar o engine
config := protoagent.EngineConfig{
OutputDir: "./output",
Workspace: "./workspace",
EnableOPA: true,
EnableAI: false,
DryRun: false,
}
engine := protoagent.NewEngine(config)
// Definir requisitos
reqs := &protoagent.RequirementsDocument{
Version: "1.0.0",
Name: "Customer Support Bot",
Description: "Automated customer support assistant",
FunctionalRequirements: []protoagent.FunctionalRequirement{
{
ID: "FR001",
Type: "action",
Name: "HandleTicket",
Description: "Process customer support tickets",
Inputs: []protoagent.ParameterDef{
{Name: "ticket_id", Type: "string", Required: true},
{Name: "message", Type: "string", Required: true},
},
InteractionMethods: []protoagent.InteractionMethod{
protoagent.InteractionMessaging,
protoagent.InteractionAPI,
},
},
},
SecurityRequirements: []protoagent.SecurityRequirement{
{
Roles: []string{"admin", "agent", "customer"},
Permissions: []string{"read", "write", "resolve"},
SecurityControls: []string{"authentication", "authorization"},
},
},
}
// Processar requisitos e gerar artefatos
ctx := context.Background()
artifacts, err := engine.ProcessRequirements(ctx, reqs)
if err != nil {
panic(err)
}
// Usar artefatos gerados
if artifacts.AgentConfig != nil {
// Salvar AGENT.md
}
for _, policy := range artifacts.Policies {
// Salvar políticas OPA
}
}
```
## Políticas OPA
O ProtoAgent gera automaticamente políticas Rego para Open Policy Agent baseadas nos requisitos de segurança:
### RBAC (Role-Based Access Control)
```rego
package authz.rbac
default allow = false
roles := {"admin", "user", "viewer"}
role_permissions := {
"admin": {"read", "write", "delete", "admin"},
"user": {"read", "write"},
"viewer": {"read"}
}
allow {
some role in input.user.roles
some perm in role_permissions[role]
perm == input.permission
}
```
### Controle de Acesso a Dados
```rego
package authz.data_access
default allow = false
allow {
input.data_classification == "public"
}
allow {
input.data_classification == "confidential"
input.user.clearance_level >= 2
}
```
## Workflow de Desenvolvimento
1. **Definir Requisitos**: Crie um documento YAML/JSON com requisitos funcionais e não-funcionais
2. **Processar**: Execute o ProtoAgent para gerar artefatos
3. **Revisar**: Analise os artefatos gerados
4. **Customizar**: Ajuste conforme necessário
5. **Implantar**: Use os artefatos no seu workspace PicoClaw
## Integração com PicoClaw
Os artefatos gerados pelo ProtoAgent são compatíveis com a estrutura do PicoClaw:
- `AGENT.md` → Configuração do agente
- `skills/` → Habilidades personalizadas
- `workspace/memory/` → Esquemas de memória
- Políticas OPA → Controle de acesso
## Próximos Passos
- [ ] Suporte a provedores de IA para geração assistida
- [ ] Validação de políticas OPA com OPA CLI
- [ ] Templates customizáveis por domínio
- [ ] Export para Docker Compose/Kubernetes
- [ ] Interface web para definição de requisitos
## Licença
Mesma licença do PicoClaw original.

314
pkg/protoagent/engine.go Normal file
View file

@ -0,0 +1,314 @@
package protoagent
import (
"context"
"fmt"
"strings"
"time"
"github.com/sipeed/picoclaw/pkg/logger"
)
// Engine is the main prototyping engine that transforms requirements into artifacts.
type Engine struct {
config EngineConfig
}
// EngineConfig holds configuration for the prototyping engine.
type EngineConfig struct {
OutputDir string `json:"outputDir" yaml:"outputDir"`
Workspace string `json:"workspace" yaml:"workspace"`
EnableOPA bool `json:"enableOPA" yaml:"enableOPA"`
EnableAI bool `json:"enableAI" yaml:"enableAI"`
AIProvider string `json:"aiProvider,omitempty" yaml:"aiProvider,omitempty"`
DryRun bool `json:"dryRun" yaml:"dryRun"`
Verbose bool `json:"verbose" yaml:"verbose"`
}
// NewEngine creates a new prototyping engine.
func NewEngine(config EngineConfig) *Engine {
return &Engine{
config: config,
}
}
// ProcessRequirements takes a requirements document and generates all artifacts.
func (e *Engine) ProcessRequirements(ctx context.Context, reqs *RequirementsDocument) (*GeneratedArtifacts, error) {
logger.InfoCF("protoagent", "Starting requirements processing", map[string]any{
"name": reqs.Name,
"version": reqs.Version,
"fr_count": len(reqs.FunctionalRequirements),
"nfr_count": len(reqs.NonFunctionalRequirements),
})
artifacts := &GeneratedArtifacts{
Timestamp: time.Now(),
}
// Validate requirements first
if err := e.validateRequirements(reqs); err != nil {
return nil, fmt.Errorf("validation failed: %w", err)
}
// Generate agent configuration
agentConfig, err := e.generateAgentConfig(reqs)
if err != nil {
logger.WarnCF("protoagent", "Failed to generate agent config", map[string]any{
"error": err.Error(),
})
} else {
artifacts.AgentConfig = agentConfig
}
// Generate database schemas
dbSchemas, err := e.generateDatabaseSchemas(reqs)
if err != nil {
logger.WarnCF("protoagent", "Failed to generate database schemas", map[string]any{
"error": err.Error(),
})
} else {
artifacts.DatabaseSchemas = dbSchemas
}
// Generate interfaces
interfaces, err := e.generateInterfaces(reqs)
if err != nil {
logger.WarnCF("protoagent", "Failed to generate interfaces", map[string]any{
"error": err.Error(),
})
} else {
artifacts.Interfaces = interfaces
}
// Generate communication channels
channels, err := e.generateChannels(reqs)
if err != nil {
logger.WarnCF("protoagent", "Failed to generate channels", map[string]any{
"error": err.Error(),
})
} else {
artifacts.Channels = channels
}
// Generate OPA policies if enabled
if e.config.EnableOPA {
policies, err := e.generateOPAPolicies(reqs)
if err != nil {
logger.WarnCF("protoagent", "Failed to generate OPA policies", map[string]any{
"error": err.Error(),
})
} else {
artifacts.Policies = policies
}
}
// Generate skills
skills, err := e.generateSkills(reqs)
if err != nil {
logger.WarnCF("protoagent", "Failed to generate skills", map[string]any{
"error": err.Error(),
})
} else {
artifacts.Skills = skills
}
// Generate tools
tools, err := e.generateTools(reqs)
if err != nil {
logger.WarnCF("protoagent", "Failed to generate tools", map[string]any{
"error": err.Error(),
})
} else {
artifacts.Tools = tools
}
// Generate MCP configuration
mcpConfig, err := e.generateMCPConfig(reqs)
if err != nil {
logger.WarnCF("protoagent", "Failed to generate MCP config", map[string]any{
"error": err.Error(),
})
} else {
artifacts.MCPConfig = mcpConfig
}
// Generate validation report
artifacts.ValidationReport = e.generateValidationReport(reqs, artifacts)
logger.InfoCF("protoagent", "Requirements processing completed", map[string]any{
"name": reqs.Name,
"artifacts_count": e.countArtifacts(artifacts),
})
return artifacts, nil
}
// validateRequirements performs validation on the requirements document.
func (e *Engine) validateRequirements(reqs *RequirementsDocument) error {
var errors []ValidationError
var warnings []ValidationWarning
// Check for required fields
if reqs.Name == "" {
errors = append(errors, ValidationError{
Field: "name",
Message: "Name is required",
})
}
if len(reqs.FunctionalRequirements) == 0 {
warnings = append(warnings, ValidationWarning{
Field: "functionalRequirements",
Message: "No functional requirements defined",
})
}
// Validate FR IDs are unique
frIDs := make(map[string]bool)
for i, fr := range reqs.FunctionalRequirements {
if fr.ID == "" {
errors = append(errors, ValidationError{
Field: fmt.Sprintf("functionalRequirements[%d].id", i),
Message: "ID is required for each functional requirement",
})
} else if frIDs[fr.ID] {
errors = append(errors, ValidationError{
Field: fmt.Sprintf("functionalRequirements[%d].id", i),
Message: fmt.Sprintf("Duplicate ID: %s", fr.ID),
})
}
frIDs[fr.ID] = true
}
// Validate NFR categories
validCategories := map[string]bool{
"security": true, "performance": true, "reliability": true,
"scalability": true, "availability": true, "maintainability": true,
}
for i, nfr := range reqs.NonFunctionalRequirements {
if nfr.Category != "" && !validCategories[nfr.Category] {
warnings = append(warnings, ValidationWarning{
Field: fmt.Sprintf("nonFunctionalRequirements[%d].category", i),
Message: fmt.Sprintf("Unknown category: %s", nfr.Category),
})
}
}
// Check for missing interaction methods
for i, fr := range reqs.FunctionalRequirements {
if len(fr.InteractionMethods) == 0 {
warnings = append(warnings, ValidationWarning{
Field: fmt.Sprintf("functionalRequirements[%d].interactionMethods", i),
Message: "No interaction methods specified",
})
}
}
if len(errors) > 0 {
return fmt.Errorf("validation failed with %d errors", len(errors))
}
return nil
}
// generateAgentConfig creates the agent configuration from requirements.
func (e *Engine) generateAgentConfig(reqs *RequirementsDocument) (*AgentConfig, error) {
config := &AgentConfig{
Name: reqs.Name,
Description: reqs.Description,
}
// Extract tools from functional requirements
toolSet := make(map[string]bool)
for _, fr := range reqs.FunctionalRequirements {
for _, method := range fr.InteractionMethods {
switch method {
case InteractionAPI:
toolSet["api_client"] = true
case InteractionMCP:
toolSet["mcp_client"] = true
case InteractionMessaging:
toolSet["message_handler"] = true
case InteractionWebhook:
toolSet["webhook_handler"] = true
case InteractionDatabase:
toolSet["database_tool"] = true
case InteractionFile:
toolSet["file_tool"] = true
}
}
}
for tool := range toolSet {
config.Tools = append(config.Tools, tool)
}
// Build agent body from requirements
var body strings.Builder
body.WriteString(fmt.Sprintf("# %s Agent\n\n", config.Name))
body.WriteString(fmt.Sprintf("## Description\n\n%s\n\n", config.Description))
body.WriteString("## Generated Capabilities\n\n")
body.WriteString("This agent was automatically generated from requirements specification.\n\n")
body.WriteString("### Functional Requirements\n\n")
for _, fr := range reqs.FunctionalRequirements {
body.WriteString(fmt.Sprintf("- **%s**: %s\n", fr.Name, fr.Description))
}
body.WriteString("\n### Non-Functional Requirements\n\n")
for _, nfr := range reqs.NonFunctionalRequirements {
body.WriteString(fmt.Sprintf("- **%s** (%s): %s\n", nfr.Name, nfr.Category, nfr.Description))
}
body.WriteString("\n## Instructions\n\n")
body.WriteString("Follow the generated policies and use the provided tools to fulfill the requirements.\n")
config.Body = body.String()
return config, nil
}
// countArtifacts returns the total count of generated artifacts.
func (e *Engine) countArtifacts(artifacts *GeneratedArtifacts) int {
count := 0
if artifacts.AgentConfig != nil {
count++
}
count += len(artifacts.DatabaseSchemas)
count += len(artifacts.Interfaces)
count += len(artifacts.Channels)
count += len(artifacts.Policies)
count += len(artifacts.Skills)
count += len(artifacts.Tools)
return count
}
// generateValidationReport creates a validation report for the generated artifacts.
func (e *Engine) generateValidationReport(reqs *RequirementsDocument, artifacts *GeneratedArtifacts) *ValidationReport {
report := &ValidationReport{
Valid: true,
}
// Check if essential artifacts were generated
if artifacts.AgentConfig == nil {
report.Valid = false
report.Errors = append(report.Errors, ValidationError{
Field: "agentConfig",
Message: "Failed to generate agent configuration",
})
}
// Add suggestions based on requirements
if len(reqs.SecurityRequirements) > 0 && len(artifacts.Policies) == 0 {
report.Suggestions = append(report.Suggestions,
"Consider enabling OPA for security policy enforcement")
}
if len(reqs.FunctionalRequirements) > 10 && len(artifacts.Skills) == 0 {
report.Suggestions = append(report.Suggestions,
"Consider creating skills for complex functional requirements")
}
return report
}

View file

@ -0,0 +1,415 @@
package protoagent
import (
"fmt"
"strings"
)
// generateDatabaseSchemas creates database schemas from requirements.
func (e *Engine) generateDatabaseSchemas(reqs *RequirementsDocument) ([]DatabaseSchema, error) {
var schemas []DatabaseSchema
// Analyze requirements to determine data entities
entities := e.extractDataEntities(reqs)
if len(entities) == 0 {
// Create a default schema if no entities detected
schemas = append(schemas, DatabaseSchema{
Name: "default",
Type: "sql",
Tables: []TableDef{
{
Name: "entities",
Columns: []ColumnDef{
{Name: "id", Type: "uuid", PrimaryKey: true},
{Name: "name", Type: "varchar(255)", Nullable: false},
{Name: "created_at", Type: "timestamp", Default: "CURRENT_TIMESTAMP"},
{Name: "updated_at", Type: "timestamp"},
},
},
},
})
return schemas, nil
}
// Generate schema for each entity
for _, entity := range entities {
schema := DatabaseSchema{
Name: entity.Name,
Type: "sql",
}
table := TableDef{
Name: strings.ToLower(entity.Name) + "s",
Columns: []ColumnDef{
{Name: "id", Type: "uuid", PrimaryKey: true},
{Name: "created_at", Type: "timestamp", Default: "CURRENT_TIMESTAMP"},
{Name: "updated_at", Type: "timestamp"},
},
}
// Add columns based on entity attributes
for _, attr := range entity.Attributes {
col := ColumnDef{
Name: strings.ToLower(attr.Name),
Type: e.mapTypeToSQL(attr.Type),
Nullable: !attr.Required,
}
table.Columns = append(table.Columns, col)
}
schema.Tables = append(schema.Tables, table)
schemas = append(schemas, schema)
}
return schemas, nil
}
// Entity represents a data entity extracted from requirements.
type Entity struct {
Name string
Attributes []Attribute
}
// Attribute represents an entity attribute.
type Attribute struct {
Name string
Type string
Required bool
}
// extractDataEntities analyzes requirements to find data entities.
func (e *Engine) extractDataEntities(reqs *RequirementsDocument) []Entity {
entityMap := make(map[string]*Entity)
// Extract entities from functional requirements
for _, fr := range reqs.FunctionalRequirements {
// Look for resource-related requirements
if fr.Type == "resource" || strings.Contains(strings.ToLower(fr.Description), "store") ||
strings.Contains(strings.ToLower(fr.Description), "manage") {
entityName := e.extractEntityName(fr)
if entityName != "" {
if _, exists := entityMap[entityName]; !exists {
entityMap[entityName] = &Entity{
Name: entityName,
Attributes: []Attribute{},
}
}
// Extract attributes from inputs/outputs
for _, input := range fr.Inputs {
attr := Attribute{
Name: input.Name,
Type: input.Type,
Required: input.Required,
}
entityMap[entityName].Attributes = append(entityMap[entityName].Attributes, attr)
}
}
}
}
// Convert map to slice
var entities []Entity
for _, entity := range entityMap {
entities = append(entities, *entity)
}
return entities
}
// extractEntityName tries to extract an entity name from a requirement.
func (e *Engine) extractEntityName(fr FunctionalRequirement) string {
// Try to extract from name
name := strings.ToLower(fr.Name)
// Common entity patterns
patterns := []string{"user", "account", "order", "product", "item", "record", "data", "document"}
for _, pattern := range patterns {
if strings.Contains(name, pattern) {
return strings.Title(pattern)
}
}
// Use the requirement name as fallback
if fr.Name != "" {
return fr.Name
}
return ""
}
// mapTypeToSQL maps a generic type to SQL type.
func (e *Engine) mapTypeToSQL(t string) string {
switch strings.ToLower(t) {
case "string", "text":
return "varchar(255)"
case "int", "integer", "number":
return "integer"
case "float", "double", "decimal":
return "decimal(10,2)"
case "bool", "boolean":
return "boolean"
case "date":
return "date"
case "datetime", "timestamp":
return "timestamp"
case "json":
return "jsonb"
default:
return "text"
}
}
// generateInterfaces creates interface definitions from requirements.
func (e *Engine) generateInterfaces(reqs *RequirementsDocument) ([]InterfaceDef, error) {
var interfaces []InterfaceDef
// Check for UI interaction methods
hasUI := false
hasAPI := false
for _, fr := range reqs.FunctionalRequirements {
for _, method := range fr.InteractionMethods {
if method == InteractionUI {
hasUI = true
}
if method == InteractionAPI {
hasAPI = true
}
}
}
// Generate API interface if needed
if hasAPI {
apiInterface := InterfaceDef{
Name: "API",
Type: "api",
}
// Create endpoints from functional requirements
for _, fr := range reqs.FunctionalRequirements {
endpoint := EndpointDef{
Path: fmt.Sprintf("/api/v1/%s", strings.ToLower(fr.Name)),
Method: "POST",
Description: fr.Description,
Inputs: fr.Inputs,
Outputs: fr.Outputs,
}
apiInterface.Endpoints = append(apiInterface.Endpoints, endpoint)
}
interfaces = append(interfaces, apiInterface)
}
// Generate Web UI interface if needed
if hasUI {
webInterface := InterfaceDef{
Name: "Web UI",
Type: "web",
}
// Create screens from functional requirements
for _, fr := range reqs.FunctionalRequirements {
screen := ScreenDef{
Name: fr.Name,
Route: fmt.Sprintf("/%s", strings.ToLower(fr.Name)),
}
// Add components based on inputs
for _, input := range fr.Inputs {
component := ComponentDef{
Name: input.Name,
Type: e.inputTypeToComponent(input.Type),
Properties: map[string]string{
"label": input.Name,
"required": fmt.Sprintf("%v", input.Required),
},
}
screen.Components = append(screen.Components, component)
}
webInterface.Screens = append(webInterface.Screens, screen)
}
interfaces = append(interfaces, webInterface)
}
return interfaces, nil
}
// inputTypeToComponent maps input types to UI components.
func (e *Engine) inputTypeToComponent(t string) string {
switch strings.ToLower(t) {
case "string", "text":
return "TextInput"
case "int", "integer", "number", "float":
return "NumberInput"
case "bool", "boolean":
return "Checkbox"
case "date":
return "DatePicker"
case "datetime", "timestamp":
return "DateTimePicker"
default:
return "TextInput"
}
}
// generateChannels creates communication channel configurations.
func (e *Engine) generateChannels(reqs *RequirementsDocument) ([]ChannelConfig, error) {
var channels []ChannelConfig
// Check for messaging interaction methods
for _, fr := range reqs.FunctionalRequirements {
for _, method := range fr.InteractionMethods {
if method == InteractionMessaging {
// Add default channels based on requirements
channels = append(channels, ChannelConfig{
Name: "telegram",
Type: "telegram",
Enabled: true,
Config: map[string]string{
"token": "${TELEGRAM_BOT_TOKEN}",
},
})
break
}
}
}
// Check for webhook requirements
for _, fr := range reqs.FunctionalRequirements {
for _, method := range fr.InteractionMethods {
if method == InteractionWebhook {
channels = append(channels, ChannelConfig{
Name: "webhook",
Type: "webhook",
Enabled: true,
Config: map[string]string{
"path": "/webhook",
"secret": "${WEBHOOK_SECRET}",
},
})
break
}
}
}
return channels, nil
}
// generateSkills creates skill definitions from requirements.
func (e *Engine) generateSkills(reqs *RequirementsDocument) ([]SkillDefinition, error) {
var skills []SkillDefinition
// Generate skills for complex operations
for _, fr := range reqs.FunctionalRequirements {
if fr.Type == "operation" && len(fr.Preconditions) > 0 {
skill := SkillDefinition{
Name: fmt.Sprintf("%s_skill", strings.ToLower(fr.Name)),
Description: fr.Description,
Triggers: []string{fr.Name},
}
// Generate skill code template
code := fmt.Sprintf(`// Auto-generated skill for: %s
package skills
import "context"
func %sSkill(ctx context.Context, params map[string]interface{}) (interface{}, error) {
// TODO: Implement skill logic
// Preconditions: %v
return nil, nil
}
`, fr.Description, strings.ToLower(fr.Name), fr.Preconditions)
skill.Code = code
skills = append(skills, skill)
}
}
return skills, nil
}
// generateTools creates tool definitions from requirements.
func (e *Engine) generateTools(reqs *RequirementsDocument) ([]ToolDefinition, error) {
var tools []ToolDefinition
// Generate tools based on interaction methods
toolSet := make(map[string]bool)
for _, fr := range reqs.FunctionalRequirements {
for _, method := range fr.InteractionMethods {
toolKey := string(method)
if !toolSet[toolKey] {
toolSet[toolKey] = true
tool := ToolDefinition{
Name: string(method) + "_tool",
Description: fmt.Sprintf("Tool for %s interactions", method),
Type: "custom",
}
switch method {
case InteractionAPI:
tool.Config = map[string]string{
"type": "http",
"base_url": "${API_BASE_URL}",
}
case InteractionDatabase:
tool.Config = map[string]string{
"type": "database",
"driver": "postgres",
"dsn": "${DATABASE_URL}",
}
case InteractionFile:
tool.Config = map[string]string{
"type": "filesystem",
"root": "${WORKSPACE_DIR}",
}
}
tools = append(tools, tool)
}
}
}
return tools, nil
}
// generateMCPConfig creates MCP server configuration.
func (e *Engine) generateMCPConfig(reqs *RequirementsDocument) (*MCPConfiguration, error) {
var mcpConfig MCPConfiguration
// Check for MCP interaction requirements
hasMCP := false
for _, fr := range reqs.FunctionalRequirements {
for _, method := range fr.InteractionMethods {
if method == InteractionMCP {
hasMCP = true
break
}
}
if hasMCP {
break
}
}
if hasMCP {
mcpConfig.Servers = []MCPServerConfig{
{
Name: "default",
Type: "stdio",
Command: "mcp-server",
Args: []string{"--config", "${MCP_CONFIG_PATH}"},
},
}
}
if len(mcpConfig.Servers) == 0 {
return nil, nil
}
return &mcpConfig, nil
}

230
pkg/protoagent/policies.go Normal file
View file

@ -0,0 +1,230 @@
package protoagent
import (
"fmt"
"strings"
)
// generateOPAPolicies creates Open Policy Agent policies from security requirements.
func (e *Engine) generateOPAPolicies(reqs *RequirementsDocument) ([]PolicyDefinition, error) {
var policies []PolicyDefinition
// Generate RBAC policy if security requirements exist
if len(reqs.SecurityRequirements) > 0 {
rbacPolicy := e.generateRBACPolicy(reqs)
policies = append(policies, rbacPolicy)
}
// Generate authorization policies from NFRs
for _, nfr := range reqs.NonFunctionalRequirements {
if nfr.Category == "security" {
authPolicy := e.generateAuthorizationPolicy(nfr)
if authPolicy != nil {
policies = append(policies, *authPolicy)
}
}
}
// Generate data access policies
dataPolicy := e.generateDataAccessPolicy(reqs)
if dataPolicy != nil {
policies = append(policies, *dataPolicy)
}
return policies, nil
}
// generateRBACPolicy creates a Role-Based Access Control policy.
func (e *Engine) generateRBACPolicy(reqs *RequirementsDocument) PolicyDefinition {
// Collect all roles from security requirements
roleSet := make(map[string]bool)
permissionSet := make(map[string]bool)
for _, secReq := range reqs.SecurityRequirements {
for _, role := range secReq.Roles {
roleSet[role] = true
}
for _, perm := range secReq.Permissions {
permissionSet[perm] = true
}
}
// Add default roles if none specified
if len(roleSet) == 0 {
roleSet["admin"] = true
roleSet["user"] = true
roleSet["viewer"] = true
}
// Build Rego policy
var rego strings.Builder
rego.WriteString("package authz.rbac\n\n")
rego.WriteString("# Auto-generated RBAC policy from requirements\n\n")
rego.WriteString("# Default deny\n")
rego.WriteString("default allow = false\n\n")
rego.WriteString("# Role definitions\n")
rego.WriteString("roles := {\n")
for role := range roleSet {
rego.WriteString(fmt.Sprintf(" \"%s\",\n", role))
}
rego.WriteString("}\n\n")
rego.WriteString("# Permission definitions\n")
rego.WriteString("permissions := {\n")
for perm := range permissionSet {
rego.WriteString(fmt.Sprintf(" \"%s\",\n", perm))
}
rego.WriteString("}\n\n")
rego.WriteString("# Role-permission mapping\n")
rego.WriteString("role_permissions := {\n")
rego.WriteString(" \"admin\": {\"read\", \"write\", \"delete\", \"admin\"},\n")
rego.WriteString(" \"user\": {\"read\", \"write\"},\n")
rego.WriteString(" \"viewer\": {\"read\"}\n")
rego.WriteString("}\n\n")
rego.WriteString("# Allow if user has required permission\n")
rego.WriteString("allow {\n")
rego.WriteString(" some role in input.user.roles\n")
rego.WriteString(" some perm in role_permissions[role]\n")
rego.WriteString(" perm == input.permission\n")
rego.WriteString("}\n\n")
rego.WriteString("# Admin bypass\n")
rego.WriteString("allow {\n")
rego.WriteString(" some role in input.user.roles\n")
rego.WriteString(" role == \"admin\"\n")
rego.WriteString("}\n")
return PolicyDefinition{
Name: "rbac_policy",
Package: "authz.rbac",
Description: "Role-Based Access Control policy",
Rego: rego.String(),
}
}
// generateAuthorizationPolicy creates an authorization policy from NFR.
func (e *Engine) generateAuthorizationPolicy(nfr NonFunctionalRequirement) *PolicyDefinition {
if len(nfr.Constraints) == 0 {
return nil
}
var rego strings.Builder
rego.WriteString("package authz.custom\n\n")
rego.WriteString(fmt.Sprintf("# Policy: %s\n", nfr.Name))
rego.WriteString(fmt.Sprintf("# Description: %s\n\n", nfr.Description))
rego.WriteString("default allow = false\n\n")
// Generate rules from constraints
for constraint, value := range nfr.Constraints {
ruleName := strings.ReplaceAll(strings.ToLower(constraint), " ", "_")
rego.WriteString(fmt.Sprintf("%s {\n", ruleName))
rego.WriteString(fmt.Sprintf(" input.%s == \"%s\"\n", constraint, value))
rego.WriteString("}\n\n")
}
rego.WriteString("allow {\n")
for constraint := range nfr.Constraints {
ruleName := strings.ReplaceAll(strings.ToLower(constraint), " ", "_")
rego.WriteString(fmt.Sprintf(" %s\n", ruleName))
}
rego.WriteString("}\n")
return &PolicyDefinition{
Name: fmt.Sprintf("%s_policy", strings.ToLower(nfr.Name)),
Package: "authz.custom",
Description: nfr.Description,
Rego: rego.String(),
}
}
// generateDataAccessPolicy creates data access control policies.
func (e *Engine) generateDataAccessPolicy(reqs *RequirementsDocument) *PolicyDefinition {
if len(reqs.SecurityRequirements) == 0 {
return nil
}
var hasDataClassification bool
for _, secReq := range reqs.SecurityRequirements {
if secReq.DataClassification != "" {
hasDataClassification = true
break
}
}
if !hasDataClassification {
return nil
}
var rego strings.Builder
rego.WriteString("package authz.data_access\n\n")
rego.WriteString("# Data access control policy based on classification\n\n")
rego.WriteString("default allow = false\n\n")
rego.WriteString("# Allow access based on data classification\n")
rego.WriteString("allow {\n")
rego.WriteString(" input.data_classification == \"public\"\n")
rego.WriteString("}\n\n")
rego.WriteString("allow {\n")
rego.WriteString(" input.data_classification == \"internal\"\n")
rego.WriteString(" input.user.clearance_level >= 1\n")
rego.WriteString("}\n\n")
rego.WriteString("allow {\n")
rego.WriteString(" input.data_classification == \"confidential\"\n")
rego.WriteString(" input.user.clearance_level >= 2\n")
rego.WriteString(" input.user.department == input.data.owner_department\n")
rego.WriteString("}\n\n")
rego.WriteString("allow {\n")
rego.WriteString(" input.data_classification == \"restricted\"\n")
rego.WriteString(" input.user.clearance_level >= 3\n")
rego.WriteString(" input.purpose == \"authorized\"\n")
rego.WriteString("}\n")
return &PolicyDefinition{
Name: "data_access_policy",
Package: "authz.data_access",
Description: "Data access control based on classification levels",
Rego: rego.String(),
}
}
// validateOPAPolicies validates generated OPA policies.
func (e *Engine) validateOPAPolicies(policies []PolicyDefinition) []ValidationError {
var errors []ValidationError
for i, policy := range policies {
// Check for required fields
if policy.Package == "" {
errors = append(errors, ValidationError{
Field: fmt.Sprintf("policies[%d].package", i),
Message: "Package is required",
})
}
if policy.Rego == "" {
errors = append(errors, ValidationError{
Field: fmt.Sprintf("policies[%d].rego", i),
Message: "Rego code is required",
})
}
// Basic syntax validation
if !strings.Contains(policy.Rego, "package ") {
errors = append(errors, ValidationError{
Field: fmt.Sprintf("policies[%d].rego", i),
Message: "Missing package declaration",
})
}
}
return errors
}

307
pkg/protoagent/types.go Normal file
View file

@ -0,0 +1,307 @@
// Package protoagent provides a behavior prototyping tool that transforms
// functional and non-functional requirements into working agent configurations,
// databases, interfaces, and communication channels.
package protoagent
import (
"encoding/json"
"time"
)
// InteractionMethod defines how the agent interacts with external systems.
type InteractionMethod string
const (
InteractionAPI InteractionMethod = "api"
InteractionMCP InteractionMethod = "mcp"
InteractionUI InteractionMethod = "ui"
InteractionMessaging InteractionMethod = "messaging"
InteractionWebhook InteractionMethod = "webhook"
InteractionCLI InteractionMethod = "cli"
InteractionDatabase InteractionMethod = "database"
InteractionFile InteractionMethod = "file"
InteractionEventBus InteractionMethod = "eventbus"
)
// FunctionalRequirement describes what the system should do.
type FunctionalRequirement struct {
ID string `json:"id" yaml:"id"`
Type string `json:"type" yaml:"type"` // action, operation, actor, resource
Name string `json:"name" yaml:"name"`
Description string `json:"description" yaml:"description"`
Inputs []ParameterDef `json:"inputs,omitempty" yaml:"inputs,omitempty"`
Outputs []ParameterDef `json:"outputs,omitempty" yaml:"outputs,omitempty"`
Preconditions []string `json:"preconditions,omitempty" yaml:"preconditions,omitempty"`
Postconditions []string `json:"postconditions,omitempty" yaml:"postconditions,omitempty"`
InteractionMethods []InteractionMethod `json:"interactionMethods,omitempty" yaml:"interactionMethods,omitempty"`
Priority int `json:"priority,omitempty" yaml:"priority,omitempty"`
Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
}
// NonFunctionalRequirement describes constraints and quality attributes.
type NonFunctionalRequirement struct {
ID string `json:"id" yaml:"id"`
Category string `json:"category" yaml:"category"` // security, performance, reliability, scalability
Name string `json:"name" yaml:"name"`
Description string `json:"description" yaml:"description"`
Constraints map[string]string `json:"constraints,omitempty" yaml:"constraints,omitempty"`
Metrics []MetricDef `json:"metrics,omitempty" yaml:"metrics,omitempty"`
Priority int `json:"priority,omitempty" yaml:"priority,omitempty"`
Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
}
// ParameterDef defines a parameter for inputs/outputs.
type ParameterDef struct {
Name string `json:"name" yaml:"name"`
Type string `json:"type" yaml:"type"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
Default string `json:"default,omitempty" yaml:"default,omitempty"`
}
// MetricDef defines a measurable metric for NFRs.
type MetricDef struct {
Name string `json:"name" yaml:"name"`
Target string `json:"target" yaml:"target"`
Threshold float64 `json:"threshold,omitempty" yaml:"threshold,omitempty"`
Unit string `json:"unit,omitempty" yaml:"unit,omitempty"`
}
// SecurityRequirement captures security-specific NFRs.
type SecurityRequirement struct {
Permissions []string `json:"permissions,omitempty" yaml:"permissions,omitempty"`
Roles []string `json:"roles,omitempty" yaml:"roles,omitempty"`
Authorizations []string `json:"authorizations,omitempty" yaml:"authorizations,omitempty"`
SecurityControls []string `json:"securityControls,omitempty" yaml:"securityControls,omitempty"`
DataClassification string `json:"dataClassification,omitempty" yaml:"dataClassification,omitempty"`
}
// PerformanceRequirement captures performance-specific NFRs.
type PerformanceRequirement struct {
ResponseTime time.Duration `json:"responseTime,omitempty" yaml:"responseTime,omitempty"`
Throughput float64 `json:"throughput,omitempty" yaml:"throughput,omitempty"`
Concurrency int `json:"concurrency,omitempty" yaml:"concurrency,omitempty"`
ResourceLimits ResourceLimit `json:"resourceLimits,omitempty" yaml:"resourceLimits,omitempty"`
}
// ResourceLimit defines resource constraints.
type ResourceLimit struct {
Memory string `json:"memory,omitempty" yaml:"memory,omitempty"`
CPU string `json:"cpu,omitempty" yaml:"cpu,omitempty"`
Storage string `json:"storage,omitempty" yaml:"storage,omitempty"`
Network string `json:"network,omitempty" yaml:"network,omitempty"`
}
// RequirementsDocument is the complete specification input.
type RequirementsDocument struct {
Version string `json:"version" yaml:"version"`
Name string `json:"name" yaml:"name"`
Description string `json:"description" yaml:"description"`
FunctionalRequirements []FunctionalRequirement `json:"functionalRequirements" yaml:"functionalRequirements"`
NonFunctionalRequirements []NonFunctionalRequirement `json:"nonFunctionalRequirements" yaml:"nonFunctionalRequirements"`
SecurityRequirements []SecurityRequirement `json:"securityRequirements,omitempty" yaml:"securityRequirements,omitempty"`
PerformanceRequirements []PerformanceRequirement `json:"performanceRequirements,omitempty" yaml:"performanceRequirements,omitempty"`
Metadata map[string]string `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// GeneratedArtifacts represents all outputs from the prototyping process.
type GeneratedArtifacts struct {
Timestamp time.Time `json:"timestamp" yaml:"timestamp"`
AgentConfig *AgentConfig `json:"agentConfig,omitempty" yaml:"agentConfig,omitempty"`
DatabaseSchemas []DatabaseSchema `json:"databaseSchemas,omitempty" yaml:"databaseSchemas,omitempty"`
Interfaces []InterfaceDef `json:"interfaces,omitempty" yaml:"interfaces,omitempty"`
Channels []ChannelConfig `json:"channels,omitempty" yaml:"channels,omitempty"`
Policies []PolicyDefinition `json:"policies,omitempty" yaml:"policies,omitempty"`
Skills []SkillDefinition `json:"skills,omitempty" yaml:"skills,omitempty"`
Tools []ToolDefinition `json:"tools,omitempty" yaml:"tools,omitempty"`
MCPConfig *MCPConfiguration `json:"mcpConfig,omitempty" yaml:"mcpConfig,omitempty"`
ValidationReport *ValidationReport `json:"validationReport,omitempty" yaml:"validationReport,omitempty"`
}
// AgentConfig is the generated AGENT.md configuration.
type AgentConfig struct {
Name string `json:"name" yaml:"name"`
Description string `json:"description" yaml:"description"`
Tools []string `json:"tools,omitempty" yaml:"tools,omitempty"`
Model string `json:"model,omitempty" yaml:"model,omitempty"`
MaxTurns *int `json:"maxTurns,omitempty" yaml:"maxTurns,omitempty"`
Skills []string `json:"skills,omitempty" yaml:"skills,omitempty"`
MCPServers []string `json:"mcpServers,omitempty" yaml:"mcpServers,omitempty"`
Body string `json:"body" yaml:"body"`
}
// DatabaseSchema defines a database structure.
type DatabaseSchema struct {
Name string `json:"name" yaml:"name"`
Type string `json:"type" yaml:"type"` // sql, nosql, memory, file
Tables []TableDef `json:"tables,omitempty" yaml:"tables,omitempty"`
Collections []CollectionDef `json:"collections,omitempty" yaml:"collections,omitempty"`
Indexes []IndexDef `json:"indexes,omitempty" yaml:"indexes,omitempty"`
Migrations []string `json:"migrations,omitempty" yaml:"migrations,omitempty"`
}
// TableDef defines a SQL table.
type TableDef struct {
Name string `json:"name" yaml:"name"`
Columns []ColumnDef `json:"columns" yaml:"columns"`
Indexes []string `json:"indexes,omitempty" yaml:"indexes,omitempty"`
}
// ColumnDef defines a table column.
type ColumnDef struct {
Name string `json:"name" yaml:"name"`
Type string `json:"type" yaml:"type"`
Nullable bool `json:"nullable,omitempty" yaml:"nullable,omitempty"`
PrimaryKey bool `json:"primaryKey,omitempty" yaml:"primaryKey,omitempty"`
Unique bool `json:"unique,omitempty" yaml:"unique,omitempty"`
Default string `json:"default,omitempty" yaml:"default,omitempty"`
}
// CollectionDef defines a NoSQL collection.
type CollectionDef struct {
Name string `json:"name" yaml:"name"`
Schema json.RawMessage `json:"schema,omitempty" yaml:"schema,omitempty"`
}
// IndexDef defines a database index.
type IndexDef struct {
Name string `json:"name" yaml:"name"`
Table string `json:"table" yaml:"table"`
Columns []string `json:"columns" yaml:"columns"`
Unique bool `json:"unique,omitempty" yaml:"unique,omitempty"`
}
// InterfaceDef defines a user or system interface.
type InterfaceDef struct {
Name string `json:"name" yaml:"name"`
Type string `json:"type" yaml:"type"` // web, cli, api, gui
Endpoints []EndpointDef `json:"endpoints,omitempty" yaml:"endpoints,omitempty"`
Screens []ScreenDef `json:"screens,omitempty" yaml:"screens,omitempty"`
Components []ComponentDef `json:"components,omitempty" yaml:"components,omitempty"`
}
// EndpointDef defines an API endpoint.
type EndpointDef struct {
Path string `json:"path" yaml:"path"`
Method string `json:"method" yaml:"method"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Inputs []ParameterDef `json:"inputs,omitempty" yaml:"inputs,omitempty"`
Outputs []ParameterDef `json:"outputs,omitempty" yaml:"outputs,omitempty"`
Auth []string `json:"auth,omitempty" yaml:"auth,omitempty"`
RateLimit *RateLimitDef `json:"rateLimit,omitempty" yaml:"rateLimit,omitempty"`
}
// ScreenDef defines a UI screen.
type ScreenDef struct {
Name string `json:"name" yaml:"name"`
Route string `json:"route,omitempty" yaml:"route,omitempty"`
Components []ComponentDef `json:"components,omitempty" yaml:"components,omitempty"`
Actions []ActionDef `json:"actions,omitempty" yaml:"actions,omitempty"`
}
// ComponentDef defines a UI component.
type ComponentDef struct {
Name string `json:"name" yaml:"name"`
Type string `json:"type" yaml:"type"`
Properties map[string]string `json:"properties,omitempty" yaml:"properties,omitempty"`
}
// ActionDef defines a UI action.
type ActionDef struct {
Name string `json:"name" yaml:"name"`
Trigger string `json:"trigger" yaml:"trigger"`
Handler string `json:"handler" yaml:"handler"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
}
// ChannelConfig defines a communication channel.
type ChannelConfig struct {
Name string `json:"name" yaml:"name"`
Type string `json:"type" yaml:"type"` // telegram, discord, slack, webhook, etc.
Config map[string]string `json:"config" yaml:"config"`
Enabled bool `json:"enabled" yaml:"enabled"`
Commands []CommandDef `json:"commands,omitempty" yaml:"commands,omitempty"`
}
// CommandDef defines a channel command.
type CommandDef struct {
Name string `json:"name" yaml:"name"`
Description string `json:"description" yaml:"description"`
Handler string `json:"handler" yaml:"handler"`
Permissions []string `json:"permissions,omitempty" yaml:"permissions,omitempty"`
}
// RateLimitDef defines rate limiting configuration.
type RateLimitDef struct {
Requests int `json:"requests" yaml:"requests"`
Window time.Duration `json:"window" yaml:"window"`
}
// PolicyDefinition defines an OPA policy.
type PolicyDefinition struct {
Name string `json:"name" yaml:"name"`
Package string `json:"package" yaml:"package"`
Rules []PolicyRule `json:"rules,omitempty" yaml:"rules,omitempty"`
Rego string `json:"rego" yaml:"rego"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
}
// PolicyRule defines a single policy rule.
type PolicyRule struct {
Name string `json:"name" yaml:"name"`
Condition string `json:"condition" yaml:"condition"`
Effect string `json:"effect" yaml:"effect"` // allow, deny
}
// SkillDefinition defines a skill to be generated.
type SkillDefinition struct {
Name string `json:"name" yaml:"name"`
Description string `json:"description" yaml:"description"`
Code string `json:"code" yaml:"code"`
Dependencies []string `json:"dependencies,omitempty" yaml:"dependencies,omitempty"`
Triggers []string `json:"triggers,omitempty" yaml:"triggers,omitempty"`
}
// ToolDefinition defines a tool to be generated.
type ToolDefinition struct {
Name string `json:"name" yaml:"name"`
Description string `json:"description" yaml:"description"`
Type string `json:"type" yaml:"type"` // shell, api, mcp, custom
Config map[string]string `json:"config,omitempty" yaml:"config,omitempty"`
Code string `json:"code,omitempty" yaml:"code,omitempty"`
}
// MCPConfiguration defines MCP server configuration.
type MCPConfiguration struct {
Servers []MCPServerConfig `json:"servers" yaml:"servers"`
}
// MCPServerConfig defines a single MCP server.
type MCPServerConfig struct {
Name string `json:"name" yaml:"name"`
Type string `json:"type" yaml:"type"` // stdio, sse, websocket
Command string `json:"command,omitempty" yaml:"command,omitempty"`
Args []string `json:"args,omitempty" yaml:"args,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"`
Headers map[string]string `json:"headers,omitempty" yaml:"headers,omitempty"`
}
// ValidationReport contains validation results.
type ValidationReport struct {
Valid bool `json:"valid" yaml:"valid"`
Errors []ValidationError `json:"errors,omitempty" yaml:"errors,omitempty"`
Warnings []ValidationWarning `json:"warnings,omitempty" yaml:"warnings,omitempty"`
Suggestions []string `json:"suggestions,omitempty" yaml:"suggestions,omitempty"`
}
// ValidationError represents a validation error.
type ValidationError struct {
Field string `json:"field" yaml:"field"`
Message string `json:"message" yaml:"message"`
}
// ValidationWarning represents a validation warning.
type ValidationWarning struct {
Field string `json:"field" yaml:"field"`
Message string `json:"message" yaml:"message"`
}