diff --git a/.gitignore b/.gitignore index 135867842..c051b64ff 100644 --- a/.gitignore +++ b/.gitignore @@ -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 +``` \ No newline at end of file diff --git a/examples/protoagent/EXPECTED_OUTPUT.md b/examples/protoagent/EXPECTED_OUTPUT.md new file mode 100644 index 000000000..3416cf514 --- /dev/null +++ b/examples/protoagent/EXPECTED_OUTPUT.md @@ -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`. diff --git a/examples/protoagent/README.md b/examples/protoagent/README.md new file mode 100644 index 000000000..d75737165 --- /dev/null +++ b/examples/protoagent/README.md @@ -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 diff --git a/examples/protoagent/cafeteria-loyalty-system.json b/examples/protoagent/cafeteria-loyalty-system.json new file mode 100644 index 000000000..9ba8291cf --- /dev/null +++ b/examples/protoagent/cafeteria-loyalty-system.json @@ -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" + } +} diff --git a/examples/protoagent/example_cafeteria.go b/examples/protoagent/example_cafeteria.go new file mode 100644 index 000000000..c97e1c802 --- /dev/null +++ b/examples/protoagent/example_cafeteria.go @@ -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 +} diff --git a/examples/protoagent/example_travel.go b/examples/protoagent/example_travel.go new file mode 100644 index 000000000..7a7a3a87b --- /dev/null +++ b/examples/protoagent/example_travel.go @@ -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 +} diff --git a/examples/protoagent/travel-experience-platform.json b/examples/protoagent/travel-experience-platform.json new file mode 100644 index 000000000..e81b44b63 --- /dev/null +++ b/examples/protoagent/travel-experience-platform.json @@ -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"] + } +} diff --git a/go.mod b/go.mod index b7259bde7..d1328e215 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/pkg/protoagent/README.md b/pkg/protoagent/README.md new file mode 100644 index 000000000..213eb03c3 --- /dev/null +++ b/pkg/protoagent/README.md @@ -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. diff --git a/pkg/protoagent/engine.go b/pkg/protoagent/engine.go new file mode 100644 index 000000000..f7d03cb1c --- /dev/null +++ b/pkg/protoagent/engine.go @@ -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 +} diff --git a/pkg/protoagent/generators.go b/pkg/protoagent/generators.go new file mode 100644 index 000000000..04eda06c2 --- /dev/null +++ b/pkg/protoagent/generators.go @@ -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 +} diff --git a/pkg/protoagent/policies.go b/pkg/protoagent/policies.go new file mode 100644 index 000000000..0f5ff439f --- /dev/null +++ b/pkg/protoagent/policies.go @@ -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 +} diff --git a/pkg/protoagent/types.go b/pkg/protoagent/types.go new file mode 100644 index 000000000..c6f7cf720 --- /dev/null +++ b/pkg/protoagent/types.go @@ -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"` +}