From 33bfc4423b2e05c7ce87414b105a7829e6de9e8b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 18:18:16 +0000 Subject: [PATCH] =?UTF-8?q?feat(schemas):=20persona.v1=20+=20plan.v1=20?= =?UTF-8?q?=E2=80=94=20squads=20como=20conte=C3=BAdo=20(Fase=202a)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Squads são conteúdo, não código": estabelece os dois contratos que fazem a esteira BuildToValue e a galeria de personas serem dados publicáveis, não recompilação. - persona.v1 (crates/forge-schemas/src/persona.rs + schema + fixture): identidade + modelos mentais (referências canônicas da área) + princípios + autonomia DESCRITIVA (rótulo consultável, não loop automático — respeita ADR 0021) + gatilhos de ativação + contratos de handoff + formatos de entrega. Validação semântica: confidence_threshold em [0,1]. - plan.v1 (crates/forge-schemas/src/plan.rs + schema + fixture): manifesto de plano/entrega — fases → deliverables exportáveis → quality_gates → success_criteria → budget → rollback. Ponte entre squad.workflow.v1 (fiação) e os artefatos (o produto). Validação: esteira 1..=N única/sequencial. - Galeria semeada: schemas/personas/editorial/{pauteiro,redator,revisor-de- estilo,fact-checker}.json — squad Editorial da Onda 1, cada persona validada contra persona.v1 no CI. Testes (forge-schemas): persona_fixture_*, plan_fixture_*, galeria_de_personas_ valida_contra_persona_v1 — schema↔struct + validação semântica + galeria. Clippy/fmt limpos. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Ep37vHQeEet5LynX35nY2G --- crates/forge-schemas/src/lib.rs | 2 + crates/forge-schemas/src/persona.rs | 157 +++++++++++++++++ crates/forge-schemas/src/plan.rs | 158 ++++++++++++++++++ crates/forge-schemas/tests/schema_fixtures.rs | 92 ++++++++++ schemas/fixtures/persona.v1.json | 39 +++++ schemas/fixtures/plan.v1.json | 36 ++++ schemas/json/persona.v1.schema.json | 66 ++++++++ schemas/json/plan.v1.schema.json | 56 +++++++ schemas/personas/editorial/fact-checker.json | 28 ++++ schemas/personas/editorial/pauteiro.json | 28 ++++ schemas/personas/editorial/redator.json | 29 ++++ .../personas/editorial/revisor-de-estilo.json | 29 ++++ 12 files changed, 720 insertions(+) create mode 100644 crates/forge-schemas/src/persona.rs create mode 100644 crates/forge-schemas/src/plan.rs create mode 100644 schemas/fixtures/persona.v1.json create mode 100644 schemas/fixtures/plan.v1.json create mode 100644 schemas/json/persona.v1.schema.json create mode 100644 schemas/json/plan.v1.schema.json create mode 100644 schemas/personas/editorial/fact-checker.json create mode 100644 schemas/personas/editorial/pauteiro.json create mode 100644 schemas/personas/editorial/redator.json create mode 100644 schemas/personas/editorial/revisor-de-estilo.json diff --git a/crates/forge-schemas/src/lib.rs b/crates/forge-schemas/src/lib.rs index ea824ef..066ca57 100644 --- a/crates/forge-schemas/src/lib.rs +++ b/crates/forge-schemas/src/lib.rs @@ -8,6 +8,8 @@ pub mod canonical; pub mod experiment; pub mod handoff; pub mod ledger; +pub mod persona; +pub mod plan; pub mod telemetry; pub mod verification; pub mod workflow; diff --git a/crates/forge-schemas/src/persona.rs b/crates/forge-schemas/src/persona.rs new file mode 100644 index 0000000..cde1b77 --- /dev/null +++ b/crates/forge-schemas/src/persona.rs @@ -0,0 +1,157 @@ +//! Persona de squad como **conteúdo** (`persona.v1`, Fase 2 BuildToValue). +//! +//! Uma persona é um item de galeria: identidade + modelos mentais (referências +//! canônicas da área — "Clean Architecture" para um arquiteto, "Bluebook" para +//! um paralegal), princípios, escada de autonomia (DESCRITIVA — rótulo +//! consultável, não um loop automático; ADR 0021), gatilhos de ativação e +//! contratos de comunicação (handoff). "Squads são conteúdo, não código": o +//! admin publica uma persona e ela aparece na galeria — nenhuma recompilação. +//! +//! O contrato canônico vive em `schemas/json/persona.v1.schema.json`; este tipo +//! deve permanecer compatível (teste em `tests/schema_fixtures.rs`). + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Nível de autonomia DESCRITIVO (rótulo). Não dispara promoção/rebaixamento +/// automático — é metadado que o humano consulta/sobrepõe (ADR 0021). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +pub enum AutonomyLabel { + L1, + L2, + L3, + L4, + L5, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct MentalModel { + /// Referência canônica da área (livro/autor/norma). + pub reference: String, + /// Quando aplicar essa referência. + pub apply_when: String, +} + +/// Severidade de um princípio quando violado. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum PrincipleSeverity { + Low, + Medium, + High, + Critical, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct CorePrinciple { + pub id: String, + pub description: String, + /// Como validar (ex.: "manual", "static_analysis", "code_review"). + pub validation: String, + pub severity: PrincipleSeverity, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct Autonomy { + pub level: AutonomyLabel, + #[serde(default)] + pub can_decide_alone: Vec, + #[serde(default)] + pub requires_approval: Vec, + /// Se o papel pode vetar uma entrega (ex.: um Auditor/Segurança). + pub can_veto: bool, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct ActivationTriggers { + #[serde(default)] + pub semantic_patterns: Vec, + #[serde(default)] + pub context_keywords: Vec, + /// Limiar de confiança [0,1] para a persona "acender" numa tarefa. + pub confidence_threshold: f64, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct Communication { + #[serde(default)] + pub receives_from: Vec, + #[serde(default)] + pub delivers_to: Vec, + /// Descrição do contrato de handoff (o que entrega e com que critério). + pub handoff_contract: String, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct Persona { + pub id: String, + pub display_name: String, + /// Domínio/profissão (ex.: "editorial", "juridico", "musica", "software"). + pub domain: String, + #[serde(default)] + pub mental_models: Vec, + #[serde(default)] + pub core_principles: Vec, + pub autonomy: Autonomy, + pub activation_triggers: ActivationTriggers, + pub communication: Communication, + /// Formatos exportáveis que esta persona entrega (DOCX, MusicXML, PDF…). + #[serde(default)] + pub delivery_formats: Vec, +} + +impl Persona { + /// Checagem semântica além do schema: o limiar de confiança tem que estar + /// em [0,1] (um limiar fora disso nunca acenderia ou sempre acenderia — um + /// erro silencioso de galeria). Erro claro em vez de persona quebrada. + pub fn validate(&self) -> Result<(), String> { + let t = self.activation_triggers.confidence_threshold; + if !(0.0..=1.0).contains(&t) { + return Err(format!( + "confidence_threshold fora de [0,1]: {t} (persona {})", + self.id + )); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn persona_min() -> Persona { + Persona { + id: "revisor-de-estilo".into(), + display_name: "Revisor de estilo".into(), + domain: "editorial".into(), + mental_models: vec![], + core_principles: vec![], + autonomy: Autonomy { + level: AutonomyLabel::L3, + can_decide_alone: vec![], + requires_approval: vec![], + can_veto: false, + }, + activation_triggers: ActivationTriggers { + semantic_patterns: vec![], + context_keywords: vec![], + confidence_threshold: 0.6, + }, + communication: Communication { + receives_from: vec![], + delivers_to: vec![], + handoff_contract: "artigo.md + notas".into(), + }, + delivery_formats: vec![], + } + } + + #[test] + fn valida_limiar_de_confianca() { + assert!(persona_min().validate().is_ok()); + let mut p = persona_min(); + p.activation_triggers.confidence_threshold = 1.5; + assert!(p.validate().is_err()); + } +} diff --git a/crates/forge-schemas/src/plan.rs b/crates/forge-schemas/src/plan.rs new file mode 100644 index 0000000..6ee0ac8 --- /dev/null +++ b/crates/forge-schemas/src/plan.rs @@ -0,0 +1,158 @@ +//! Manifesto de plano/entrega da esteira (`plan.v1`, Fase 2 BuildToValue). +//! +//! O plano é o "ticket de trabalho" declarativo que a esteira executa: fases → +//! entregas (arquivos exportáveis: DOCX/XLSX/PDF/MusicXML) → quality gates por +//! fase → critérios de sucesso → orçamento → rollback. É a ponte entre o grafo +//! de squad (`squad.workflow.v1`, a fiação) e os artefatos exportáveis (o +//! produto). Contrato canônico em `schemas/json/plan.v1.schema.json`. + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +pub struct Prerequisites { + #[serde(default)] + pub contracts: Vec, + #[serde(default)] + pub approvals: Vec, + #[serde(default)] + pub dependencies: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct PlanPhase { + /// Ordem na esteira (1-based, única e sequencial — ver `Plan::validate`). + pub order: u32, + /// Papel/persona responsável pela fase. + pub primary_role: String, + #[serde(default)] + pub support_roles: Vec, + /// Artefatos exportáveis produzidos nesta fase (ex.: "pauta.md", + /// "partitura.musicxml"). + #[serde(default)] + pub deliverables: Vec, + /// Se a fase abre um gate humano (o membro humano aprova antes de seguir). + pub approval_required: bool, + /// Confiança estimada [0,1] da fase. + pub estimated_confidence: f64, + /// Gates de qualidade da fase (ex.: "revisao:pass", "test-coverage:85%"). + #[serde(default)] + pub quality_gates: Vec, +} + +#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +pub struct SuccessCriteria { + #[serde(default)] + pub functional: Vec, + #[serde(default)] + pub non_functional: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct Budget { + /// Custo estimado (na moeda/telemetria da plataforma). + pub estimated_cost: f64, + /// Teto de chamadas de LLM para o plano. + pub max_llm_calls: u32, +} + +#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +pub struct RollbackStrategy { + /// Se um kill-switch aborta a esteira (Fase 3 amarra ao `operational_status`). + pub kill_switch: bool, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct Plan { + #[serde(default)] + pub prerequisites: Prerequisites, + pub execution_sequence: Vec, + #[serde(default)] + pub success_criteria: SuccessCriteria, + pub budget: Budget, + #[serde(default)] + pub rollback_strategy: RollbackStrategy, +} + +impl Plan { + /// Checagens semânticas além do schema: + /// - a sequência não é vazia (um plano sem fases não produz entrega); + /// - `order` é 1..=N única e sequencial (esteira sem buracos/duplicatas); + /// - cada `estimated_confidence` está em [0,1]. + /// + /// Erro claro (um 422) em vez de um plano silenciosamente quebrado. + pub fn validate(&self) -> Result<(), String> { + if self.execution_sequence.is_empty() { + return Err("execution_sequence vazia — um plano precisa de ao menos uma fase".into()); + } + let mut orders: Vec = self.execution_sequence.iter().map(|p| p.order).collect(); + orders.sort_unstable(); + for (i, order) in orders.iter().enumerate() { + let expected = (i as u32) + 1; + if *order != expected { + return Err(format!( + "orders da esteira devem ser 1..={} únicas e sequenciais; achei {order} onde esperava {expected}", + self.execution_sequence.len() + )); + } + } + for phase in &self.execution_sequence { + if !(0.0..=1.0).contains(&phase.estimated_confidence) { + return Err(format!( + "estimated_confidence fora de [0,1] na fase {}: {}", + phase.order, phase.estimated_confidence + )); + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn phase(order: u32) -> PlanPhase { + PlanPhase { + order, + primary_role: "pauteiro".into(), + support_roles: vec![], + deliverables: vec!["pauta.md".into()], + approval_required: true, + estimated_confidence: 0.8, + quality_gates: vec!["revisao:pass".into()], + } + } + + fn plan_of(orders: &[u32]) -> Plan { + Plan { + prerequisites: Prerequisites::default(), + execution_sequence: orders.iter().map(|o| phase(*o)).collect(), + success_criteria: SuccessCriteria::default(), + budget: Budget { + estimated_cost: 0.0, + max_llm_calls: 20, + }, + rollback_strategy: RollbackStrategy { kill_switch: true }, + } + } + + #[test] + fn aceita_esteira_sequencial() { + assert!(plan_of(&[1, 2, 3]).validate().is_ok()); + } + + #[test] + fn rejeita_esteira_vazia_e_com_buraco() { + assert!(plan_of(&[]).validate().is_err()); + assert!(plan_of(&[1, 3]).validate().is_err()); + assert!(plan_of(&[1, 1]).validate().is_err()); + } + + #[test] + fn rejeita_confianca_fora_de_intervalo() { + let mut p = plan_of(&[1]); + p.execution_sequence[0].estimated_confidence = 2.0; + assert!(p.validate().is_err()); + } +} diff --git a/crates/forge-schemas/tests/schema_fixtures.rs b/crates/forge-schemas/tests/schema_fixtures.rs index 4a332a3..2ff60a5 100644 --- a/crates/forge-schemas/tests/schema_fixtures.rs +++ b/crates/forge-schemas/tests/schema_fixtures.rs @@ -21,6 +21,8 @@ use forge_schemas::experiment::ExperimentReport; use forge_schemas::handoff::HandoffEvent; use forge_schemas::ledger::LedgerEntry; +use forge_schemas::persona::Persona; +use forge_schemas::plan::Plan; use forge_schemas::telemetry::TelemetryEvent; use forge_schemas::workflow::SquadWorkflow; use jsonschema::validator_for; @@ -155,6 +157,96 @@ fn squad_workflow_fixture_valida_e_desserializa() { ); } +#[test] +fn persona_fixture_valida_e_desserializa() { + let schema = schema("persona"); + let doc = fixture("persona"); + let validator = validator_for(&schema).expect("schema compila"); + + assert!( + validator.is_valid(&doc["valid"]), + "fixture válida não bateu o schema: {:?}", + validator.iter_errors(&doc["valid"]).collect::>() + ); + let parsed: Persona = + serde_json::from_value(doc["valid"].clone()).expect("desserializa em Persona"); + assert_eq!(parsed.id, "revisor-de-estilo"); + assert!(parsed.validate().is_ok()); + + // O limiar fora de [0,1] passa pelo JSON Schema? Não — o schema também + // limita (minimum/maximum). Cobre as duas camadas (schema + validate()). + assert!( + !validator.is_valid(&doc["invalid_threshold_fora_de_intervalo"]), + "confidence_threshold=1.5 deveria reprovar o schema" + ); + let parsed_invalid: Persona = + serde_json::from_value(doc["invalid_threshold_fora_de_intervalo"].clone()) + .expect("desserializa mesmo com valor fora de intervalo (schema é quem barra)"); + assert!(parsed_invalid.validate().is_err()); +} + +#[test] +fn plan_fixture_valida_e_desserializa() { + let schema = schema("plan"); + let doc = fixture("plan"); + let validator = validator_for(&schema).expect("schema compila"); + + assert!( + validator.is_valid(&doc["valid"]), + "fixture válida não bateu o schema: {:?}", + validator.iter_errors(&doc["valid"]).collect::>() + ); + let parsed: Plan = serde_json::from_value(doc["valid"].clone()).expect("desserializa em Plan"); + assert_eq!(parsed.execution_sequence.len(), 2); + assert!(parsed.validate().is_ok()); + + assert!( + !validator.is_valid(&doc["invalid_sequence_vazia"]), + "execution_sequence vazia deveria reprovar o schema (minItems)" + ); +} + +/// A galeria semeada de personas (`schemas/personas/**/*.json`) é CONTEÚDO: +/// cada arquivo tem que bater o `persona.v1` e passar a validação semântica. +/// Uma persona quebrada na galeria é um bug de conteúdo — pega aqui, não em +/// produção. +#[test] +fn galeria_de_personas_valida_contra_persona_v1() { + let schema = schema("persona"); + let validator = validator_for(&schema).expect("schema compila"); + let dir = format!("{}/../../schemas/personas", env!("CARGO_MANIFEST_DIR")); + + fn collect(dir: &std::path::Path, out: &mut Vec) { + for entry in std::fs::read_dir(dir).unwrap() { + let path = entry.unwrap().path(); + if path.is_dir() { + collect(&path, out); + } else if path.extension().and_then(|e| e.to_str()) == Some("json") { + out.push(path); + } + } + } + + let mut files = Vec::new(); + collect(std::path::Path::new(&dir), &mut files); + assert!(!files.is_empty(), "esperava personas semeadas na galeria"); + + for file in files { + let raw = std::fs::read_to_string(&file).unwrap(); + let doc: Value = serde_json::from_str(&raw).unwrap_or_else(|e| panic!("{file:?}: {e}")); + assert!( + validator.is_valid(&doc), + "persona {file:?} não bateu o schema: {:?}", + validator.iter_errors(&doc).collect::>() + ); + let persona: Persona = serde_json::from_value(doc) + .unwrap_or_else(|e| panic!("{file:?} não desserializa: {e}")); + persona + .validate() + .unwrap_or_else(|e| panic!("{file:?} inválida: {e}")); + } +} + /// Sem tipo Rust/Python — só protege o schema em si (sintaxe/drift), não /// uma paridade de tipo. Ver nota no topo do arquivo e no `$comment` da /// fixture. diff --git a/schemas/fixtures/persona.v1.json b/schemas/fixtures/persona.v1.json new file mode 100644 index 0000000..2e9696f --- /dev/null +++ b/schemas/fixtures/persona.v1.json @@ -0,0 +1,39 @@ +{ + "$comment": "Fixture golden do contrato persona.v1. Valida contra schemas/json/persona.v1.schema.json e desserializa em forge_schemas::persona::Persona (Rust, tests/schema_fixtures.rs). Só há tipo do lado Rust hoje; a galeria (schemas/personas/*.json) consome o mesmo schema.", + "valid": { + "id": "revisor-de-estilo", + "display_name": "Revisor de estilo", + "domain": "editorial", + "mental_models": [ + { "reference": "The Elements of Style — Strunk & White", "apply_when": "clareza e concisão" } + ], + "core_principles": [ + { "id": "voz-ativa", "description": "prefira voz ativa", "validation": "manual", "severity": "medium" } + ], + "autonomy": { + "level": "L3", + "can_decide_alone": ["ajustes de estilo"], + "requires_approval": ["mudança de sentido"], + "can_veto": false + }, + "activation_triggers": { + "semantic_patterns": ["revisar", "clareza"], + "context_keywords": ["texto", "artigo"], + "confidence_threshold": 0.6 + }, + "communication": { + "receives_from": ["redator"], + "delivers_to": ["fact-checker"], + "handoff_contract": "artigo.md + notas de revisão" + }, + "delivery_formats": ["DOCX", "Markdown", "HTML"] + }, + "invalid_threshold_fora_de_intervalo": { + "id": "revisor-de-estilo", + "display_name": "Revisor de estilo", + "domain": "editorial", + "autonomy": { "level": "L3", "can_veto": false }, + "activation_triggers": { "confidence_threshold": 1.5 }, + "communication": { "handoff_contract": "artigo.md" } + } +} diff --git a/schemas/fixtures/plan.v1.json b/schemas/fixtures/plan.v1.json new file mode 100644 index 0000000..f587a74 --- /dev/null +++ b/schemas/fixtures/plan.v1.json @@ -0,0 +1,36 @@ +{ + "$comment": "Fixture golden do contrato plan.v1. Valida contra schemas/json/plan.v1.schema.json e desserializa em forge_schemas::plan::Plan (Rust, tests/schema_fixtures.rs). O invalid cobre execution_sequence vazia (minItems).", + "valid": { + "prerequisites": { "contracts": ["persona.v1"], "approvals": [], "dependencies": [] }, + "execution_sequence": [ + { + "order": 1, + "primary_role": "pauteiro", + "support_roles": [], + "deliverables": ["pauta.md"], + "approval_required": true, + "estimated_confidence": 0.8, + "quality_gates": ["revisao:pass"] + }, + { + "order": 2, + "primary_role": "redator", + "support_roles": ["revisor-de-estilo"], + "deliverables": ["artigo.docx"], + "approval_required": true, + "estimated_confidence": 0.75, + "quality_gates": ["fact-check:pass"] + } + ], + "success_criteria": { + "functional": ["artigo cobre o briefing"], + "non_functional": ["tom consistente"] + }, + "budget": { "estimated_cost": 0.5, "max_llm_calls": 20 }, + "rollback_strategy": { "kill_switch": true } + }, + "invalid_sequence_vazia": { + "execution_sequence": [], + "budget": { "estimated_cost": 0.5, "max_llm_calls": 20 } + } +} diff --git a/schemas/json/persona.v1.schema.json b/schemas/json/persona.v1.schema.json new file mode 100644 index 0000000..68b9ef3 --- /dev/null +++ b/schemas/json/persona.v1.schema.json @@ -0,0 +1,66 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://forge.buildtovalue.dev/schemas/persona.v1.schema.json", + "title": "persona.v1", + "description": "Persona de squad como conteúdo (BuildToValue): item de galeria com identidade, modelos mentais (referências canônicas da área), princípios, escada de autonomia DESCRITIVA (rótulo, não loop automático — ADR 0021), gatilhos de ativação e contratos de handoff. Publicar uma persona é conteúdo, não código.", + "type": "object", + "required": ["id", "display_name", "domain", "autonomy", "activation_triggers", "communication"], + "properties": { + "id": { "type": "string", "minLength": 1 }, + "display_name": { "type": "string", "minLength": 1 }, + "domain": { "type": "string", "minLength": 1, "examples": ["editorial", "juridico", "musica", "software", "bi"] }, + "mental_models": { + "type": "array", + "items": { + "type": "object", + "required": ["reference", "apply_when"], + "properties": { + "reference": { "type": "string" }, + "apply_when": { "type": "string" } + } + } + }, + "core_principles": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "description", "validation", "severity"], + "properties": { + "id": { "type": "string" }, + "description": { "type": "string" }, + "validation": { "type": "string" }, + "severity": { "enum": ["low", "medium", "high", "critical"] } + } + } + }, + "autonomy": { + "type": "object", + "required": ["level", "can_veto"], + "properties": { + "level": { "enum": ["L1", "L2", "L3", "L4", "L5"] }, + "can_decide_alone": { "type": "array", "items": { "type": "string" } }, + "requires_approval": { "type": "array", "items": { "type": "string" } }, + "can_veto": { "type": "boolean" } + } + }, + "activation_triggers": { + "type": "object", + "required": ["confidence_threshold"], + "properties": { + "semantic_patterns": { "type": "array", "items": { "type": "string" } }, + "context_keywords": { "type": "array", "items": { "type": "string" } }, + "confidence_threshold": { "type": "number", "minimum": 0, "maximum": 1 } + } + }, + "communication": { + "type": "object", + "required": ["handoff_contract"], + "properties": { + "receives_from": { "type": "array", "items": { "type": "string" } }, + "delivers_to": { "type": "array", "items": { "type": "string" } }, + "handoff_contract": { "type": "string" } + } + }, + "delivery_formats": { "type": "array", "items": { "type": "string" } } + } +} diff --git a/schemas/json/plan.v1.schema.json b/schemas/json/plan.v1.schema.json new file mode 100644 index 0000000..a585bc0 --- /dev/null +++ b/schemas/json/plan.v1.schema.json @@ -0,0 +1,56 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://forge.buildtovalue.dev/schemas/plan.v1.schema.json", + "title": "plan.v1", + "description": "Manifesto de plano/entrega da esteira BuildToValue: fases → entregas exportáveis (DOCX/XLSX/PDF/MusicXML) → quality gates → critérios de sucesso → orçamento → rollback. Ponte entre o grafo do squad (squad.workflow.v1) e os artefatos que são o produto.", + "type": "object", + "required": ["execution_sequence", "budget"], + "properties": { + "prerequisites": { + "type": "object", + "properties": { + "contracts": { "type": "array", "items": { "type": "string" } }, + "approvals": { "type": "array", "items": { "type": "string" } }, + "dependencies": { "type": "array", "items": { "type": "string" } } + } + }, + "execution_sequence": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["order", "primary_role", "approval_required", "estimated_confidence"], + "properties": { + "order": { "type": "integer", "minimum": 1 }, + "primary_role": { "type": "string" }, + "support_roles": { "type": "array", "items": { "type": "string" } }, + "deliverables": { "type": "array", "items": { "type": "string" } }, + "approval_required": { "type": "boolean" }, + "estimated_confidence": { "type": "number", "minimum": 0, "maximum": 1 }, + "quality_gates": { "type": "array", "items": { "type": "string" } } + } + } + }, + "success_criteria": { + "type": "object", + "properties": { + "functional": { "type": "array", "items": { "type": "string" } }, + "non_functional": { "type": "array", "items": { "type": "string" } } + } + }, + "budget": { + "type": "object", + "required": ["estimated_cost", "max_llm_calls"], + "properties": { + "estimated_cost": { "type": "number", "minimum": 0 }, + "max_llm_calls": { "type": "integer", "minimum": 0 } + } + }, + "rollback_strategy": { + "type": "object", + "properties": { + "kill_switch": { "type": "boolean" } + } + } + } +} diff --git a/schemas/personas/editorial/fact-checker.json b/schemas/personas/editorial/fact-checker.json new file mode 100644 index 0000000..a196b2f --- /dev/null +++ b/schemas/personas/editorial/fact-checker.json @@ -0,0 +1,28 @@ +{ + "id": "fact-checker", + "display_name": "Fact-checker", + "domain": "editorial", + "mental_models": [ + { "reference": "The Chicago Manual of Style — verificação de fontes", "apply_when": "conferir afirmações e citações" } + ], + "core_principles": [ + { "id": "fonte-para-tudo", "description": "toda afirmação factual tem fonte verificável", "validation": "manual", "severity": "critical" } + ], + "autonomy": { + "level": "L5", + "can_decide_alone": ["marcar afirmação sem fonte"], + "requires_approval": [], + "can_veto": true + }, + "activation_triggers": { + "semantic_patterns": ["checar", "fonte", "verificar"], + "context_keywords": ["fato", "citação", "dado"], + "confidence_threshold": 0.7 + }, + "communication": { + "receives_from": ["revisor-de-estilo"], + "delivers_to": [], + "handoff_contract": "artigo com afirmações verificadas ou marcadas como pendentes" + }, + "delivery_formats": ["DOCX", "Markdown"] +} diff --git a/schemas/personas/editorial/pauteiro.json b/schemas/personas/editorial/pauteiro.json new file mode 100644 index 0000000..d725cb1 --- /dev/null +++ b/schemas/personas/editorial/pauteiro.json @@ -0,0 +1,28 @@ +{ + "id": "pauteiro", + "display_name": "Pauteiro", + "domain": "editorial", + "mental_models": [ + { "reference": "On Writing Well — William Zinsser", "apply_when": "definir ângulo e escopo da pauta" } + ], + "core_principles": [ + { "id": "angulo-claro", "description": "toda pauta tem um ângulo e um público definidos", "validation": "manual", "severity": "high" } + ], + "autonomy": { + "level": "L3", + "can_decide_alone": ["estrutura da pauta"], + "requires_approval": ["mudança de tema"], + "can_veto": false + }, + "activation_triggers": { + "semantic_patterns": ["pauta", "briefing", "ângulo"], + "context_keywords": ["tema", "público", "tom"], + "confidence_threshold": 0.6 + }, + "communication": { + "receives_from": [], + "delivers_to": ["redator"], + "handoff_contract": "pauta.md com ângulo, público, tom e roteiro de seções" + }, + "delivery_formats": ["Markdown"] +} diff --git a/schemas/personas/editorial/redator.json b/schemas/personas/editorial/redator.json new file mode 100644 index 0000000..40316d2 --- /dev/null +++ b/schemas/personas/editorial/redator.json @@ -0,0 +1,29 @@ +{ + "id": "redator", + "display_name": "Redator", + "domain": "editorial", + "mental_models": [ + { "reference": "The Elements of Style — Strunk & White", "apply_when": "redigir com concisão" }, + { "reference": "On Writing Well — William Zinsser", "apply_when": "estruturar parágrafos e transições" } + ], + "core_principles": [ + { "id": "fidelidade-a-pauta", "description": "o texto cumpre o ângulo e o público da pauta", "validation": "code_review", "severity": "high" } + ], + "autonomy": { + "level": "L2", + "can_decide_alone": ["escolha de palavras"], + "requires_approval": ["desvio da pauta"], + "can_veto": false + }, + "activation_triggers": { + "semantic_patterns": ["redigir", "escrever", "rascunho"], + "context_keywords": ["pauta", "texto"], + "confidence_threshold": 0.55 + }, + "communication": { + "receives_from": ["pauteiro"], + "delivers_to": ["revisor-de-estilo"], + "handoff_contract": "rascunho do artigo em Markdown fiel à pauta" + }, + "delivery_formats": ["Markdown", "DOCX"] +} diff --git a/schemas/personas/editorial/revisor-de-estilo.json b/schemas/personas/editorial/revisor-de-estilo.json new file mode 100644 index 0000000..823b90d --- /dev/null +++ b/schemas/personas/editorial/revisor-de-estilo.json @@ -0,0 +1,29 @@ +{ + "id": "revisor-de-estilo", + "display_name": "Revisor de estilo", + "domain": "editorial", + "mental_models": [ + { "reference": "The Elements of Style — Strunk & White", "apply_when": "clareza e concisão" } + ], + "core_principles": [ + { "id": "voz-ativa", "description": "prefira voz ativa e frases curtas", "validation": "manual", "severity": "medium" }, + { "id": "preserva-sentido", "description": "revisão de estilo não altera o sentido do texto", "validation": "manual", "severity": "high" } + ], + "autonomy": { + "level": "L3", + "can_decide_alone": ["ajustes de estilo"], + "requires_approval": ["mudança de sentido"], + "can_veto": false + }, + "activation_triggers": { + "semantic_patterns": ["revisar", "clareza", "estilo"], + "context_keywords": ["texto", "artigo"], + "confidence_threshold": 0.6 + }, + "communication": { + "receives_from": ["redator"], + "delivers_to": ["fact-checker"], + "handoff_contract": "artigo revisado + notas de estilo" + }, + "delivery_formats": ["DOCX", "Markdown", "HTML"] +}