sinapse-ai 1.25.1 → 1.25.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -109,24 +109,24 @@ Digite *help para ver comandos disponíveis.
109
109
 
110
110
  ## Passo 4: Explorar Agentes Disponíveis
111
111
 
112
- | Agente | Ativação | Propósito |
112
+ | Ativação | Papel | Propósito |
113
113
  | ------------------- | -------------------- | ---------------------------------- |
114
- | `@dev` (Dex) | Desenvolvimento | Implementação de código, debugging |
115
- | `@qa` (Quinn) | Qualidade | Testes e validação |
116
- | `@architect` (Aria) | Arquitetura | Design de sistema e documentação |
117
- | `@pm` (Sage) | Product Manager | Requisitos e planejamento |
118
- | `@devops` (Gage) | DevOps | Git push, criação de PR, CI/CD |
119
- | `@po` (Maven) | Product Owner | Criação de stories e backlog |
120
- | `@sm` (River) | Scrum Master | Gerenciamento de sprint |
121
- | `@analyst` (Nova) | Analista de Negócios | Análise de requisitos |
114
+ | `@developer` (Pixel) | Desenvolvimento | Implementação de código, debugging |
115
+ | `@quality-gate` (Litmus) | Qualidade | Testes e validação |
116
+ | `@architect` (Stratum) | Arquitetura | Design de sistema e documentação |
117
+ | `@project-lead` (Beacon) | Product Manager | Requisitos e planejamento |
118
+ | `@devops` (Pipeline) | DevOps | Git push, criação de PR, CI/CD |
119
+ | `@product-lead` (Axis) | Product Owner | Validação de stories e backlog |
120
+ | `@sprint-lead` (Sync) | Scrum Master | Gerenciamento de sprint |
121
+ | `@analyst` (Scope) | Analista de Negócios | Análise de requisitos |
122
122
 
123
123
  ### Exemplo: Ativar Agente Desenvolvedor
124
124
 
125
- ```
126
- @dev
125
+ ```text
126
+ @developer
127
127
  ```
128
128
 
129
- O agente desenvolvedor (Dex) será ativado com uma saudação mostrando:
129
+ O agente desenvolvedor (Pixel) será ativado com uma saudação mostrando:
130
130
 
131
131
  - Status do projeto
132
132
  - Comandos rápidos
@@ -190,7 +190,7 @@ seu-projeto/
190
190
  │ │ └── quality-gates/ # Camadas de validação de qualidade
191
191
  │ ├── development/ # Assets de desenvolvimento
192
192
  │ │ ├── agents/ # Definições de agentes (12 agentes)
193
- │ │ ├── tasks/ # Workflows de tarefas (~140 tarefas)
193
+ │ │ ├── tasks/ # Workflows de development (211 development tasks)
194
194
  │ │ └── workflows/ # Workflows multi-etapa
195
195
  │ ├── product/ # Assets de produto
196
196
  │ │ ├── templates/ # Templates de documentos
@@ -232,9 +232,9 @@ npm cache clean --force
232
232
 
233
233
  ### Agente não responde
234
234
 
235
- 1. Certifique-se de estar em uma IDE com IA (Cursor, VS Code com Claude, etc.) ou Claude Code CLI
236
- 2. Verifique se está usando sintaxe correta: `@nome-do-agente`
237
- 3. Verifique se o arquivo do agente existe: `ls .sinapse-ai/development/agents/`
235
+ 1. Certifique-se de estar usando Claude Code ou Codex CLI.
236
+ 2. No Claude Code, use `@developer`; no Codex, use `$sinapse-agent developer` ou `$snps`.
237
+ 3. Verifique `.claude/agents/` para Claude Code ou `.codex/agents/` e `.agents/skills/` para Codex.
238
238
 
239
239
  ### Erros de permissão
240
240
 
@@ -141,8 +141,8 @@ O instalador automaticamente:
141
141
  - ✅ Detecta Windows e aplica configurações específicas da plataforma
142
142
  - ✅ Cria diretórios necessários com permissões apropriadas
143
143
  - ✅ Configura caminhos de IDE para localizações Windows:
144
- - Cursor: `%APPDATA%\Cursor\`
145
- - Claude: `%USERPROFILE%\.claude\`
144
+ - Agentes do Claude Code: `.claude\agents\`
145
+ - Agentes e skills do Codex: `.codex\agents\` e `.agents\skills\`
146
146
  - ✅ Lida com separadores de caminho Windows (barras invertidas)
147
147
  - ✅ Configura terminações de linha corretamente (CRLF para batch, LF para scripts)
148
148
  - ✅ Configura scripts npm compatíveis com cmd.exe e PowerShell
@@ -151,28 +151,15 @@ O instalador automaticamente:
151
151
 
152
152
  ## Configuração Específica por IDE
153
153
 
154
- ### Cursor
154
+ ### Claude Code
155
155
 
156
- 1. Baixe de [cursor.sh](https://cursor.sh/)
157
- 2. Execute o instalador
158
- 3. Regras da IDE são instaladas em `.cursor\rules\`
159
- 4. Atalho de teclado: `Ctrl+L` para abrir chat
160
- 5. Use `@nome-do-agente` para ativar agentes
156
+ 1. Os adapters de agentes são instalados em `.claude\agents\`.
157
+ 2. Use `@developer` ou outro ID canônico para ativar um agente.
161
158
 
162
- ### Claude Code (CLI)
159
+ ### Codex
163
160
 
164
- 1. Instale o Claude Code:
165
-
166
- ```powershell
167
- npm install -g @anthropic-ai/claude-code
168
- ```
169
-
170
- 2. Comandos são instalados em `.claude\commands\SINAPSE\`
171
- 3. Use `/nome-do-agente` para ativar agentes
172
-
173
-
174
- 2. Execute o instalador
175
- 4. Use `@nome-do-agente` para ativar agentes
161
+ 1. Os descritores são instalados em `.codex\agents\`, com skills em `.agents\skills\`.
162
+ 2. Use `$snps` para roteamento ou `$sinapse-agent developer` para ativação direta.
176
163
 
177
164
  ---
178
165
 
@@ -40,10 +40,11 @@ npx sinapse-ai install --target "C:\Users\My User\projeto"
40
40
 
41
41
  ## Problemas de ativação de agentes
42
42
 
43
- ### `@developer` não ativa
44
- 1. Verifique IDE detectada: `cat .claude/settings.json` (Claude Code) ou `cat .codex/instructions.md` (Codex)
45
- 2. Re-sincronize: `npx sinapse-ai install --reconfigure`
46
- 3. Reinicie o IDE
43
+ ### O agente não ativa
44
+ 1. Confirme a sintaxe do provider: `@developer` no Claude Code; `$snps` ou `$sinapse-agent developer` no Codex.
45
+ 2. Verifique a configuração instalada: `.claude/settings.json` no Claude Code ou `.codex/config.toml` no Codex.
46
+ 3. Re-sincronize: `npx sinapse-ai@latest install --reconfigure`.
47
+ 4. Reinicie o provider.
47
48
 
48
49
  ### Agente ativa mas sem comandos
49
50
  ```bash
@@ -59,12 +60,13 @@ Cheque qual hook bloqueou (mensagem de erro tem o nome). Exemplos:
59
60
  - **enforce-git-push-authority**: só `@devops` pode push. Delegue: `@devops *push`.
60
61
  - **secret-scanning**: detectou secret. Verifique e remova OU mova pra `.env` (gitignored).
61
62
 
62
- Lista completa de hooks: `.claude/hooks/README.md`.
63
+ Claude Code registra 20 hooks em `.claude/settings.json`. Codex registra 9 eventos
64
+ de lifecycle em `.codex/hooks.json` e os encaminha pelo bridge de compatibilidade.
63
65
 
64
66
  ### Hook não está rodando
65
67
  ```bash
66
- cat .claude/settings.json | grep hooks # confirma registro
67
- ls .claude/hooks/ # confirma arquivo existe
68
+ cat .claude/settings.json | grep hooks # Claude Code: confirma registro
69
+ cat .codex/hooks.json # Codex: confirma eventos registrados
68
70
  ```
69
71
 
70
72
  ## Problemas de CI
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sinapse-ai",
3
- "version": "1.25.1",
3
+ "version": "1.25.2",
4
4
  "description": "SINAPSE AI: Framework de orquestracao de IA — 17 squads, 172 agentes especializados",
5
5
  "bin": {
6
6
  "sinapse": "bin/sinapse.js",
@@ -5,8 +5,9 @@
5
5
  * @story GA-1.5 (Article gates VII/VIII/XI automated)
6
6
  *
7
7
  * Verifica que TODOS os documentos publicos referem aos MESMOS numeros de
8
- * squads/agentes/orqx/tasks que `.sinapse-ai/constitution.md` (canonico,
9
- * gerado por `npm run sync:counts`).
8
+ * squads/agentes/orqx/tasks medidos pelas fontes canonicas em disco. Contagens
9
+ * gerais reutilizam `sync-counts`; o detalhamento resolvivel usa o ativador
10
+ * parametrico do Codex.
10
11
  *
11
12
  * Documentos verificados:
12
13
  * - README.md
@@ -27,11 +28,14 @@
27
28
 
28
29
  const fs = require('fs');
29
30
  const path = require('path');
31
+ const { execFileSync } = require('child_process');
30
32
 
31
33
  const { collectCounts } = require('./sync-counts.js');
34
+ const { validateProviderAdapters } = require('./validate-provider-adapters.js');
32
35
 
33
36
  const ROOT = path.resolve(__dirname, '..');
34
37
  const CONSTITUTION = path.join(ROOT, '.sinapse-ai', 'constitution.md');
38
+ const CODEX_RESOLVER = path.join(ROOT, '.codex', 'scripts', 'resolve-codex-agent.js');
35
39
 
36
40
  /**
37
41
  * Count Constitution articles + NON-NEGOTIABLE ones from the canonical source.
@@ -56,37 +60,189 @@ function collectConstitutionCounts() {
56
60
  }
57
61
 
58
62
  const TARGETS = [
59
- { file: 'README.md', label: 'README PT' },
60
- { file: 'README.en.md', label: 'README EN' },
63
+ { file: 'README.md', label: 'README PT', requireTaskBreakdown: true },
64
+ { file: 'README.en.md', label: 'README EN', requireTaskBreakdown: true },
61
65
  { file: 'AGENTS.md', label: 'AGENTS guide' },
66
+ { file: 'docs/getting-started.md', label: 'Getting started' },
67
+ { file: 'docs/troubleshooting.md', label: 'Troubleshooting' },
68
+ { file: 'docs/guides/user-guide.md', label: 'User guide EN', requireAgentTotal: true },
69
+ { file: 'docs/pt/guides/user-guide.md', label: 'User guide PT', requireAgentTotal: true },
70
+ { file: 'docs/guides/codex-config.md', label: 'Codex configuration guide' },
71
+ { file: 'docs/guides/ide-integration.md', label: 'IDE integration guide', requireProviderBreakdown: true },
72
+ { file: 'docs/guides/project-status-feature.md', label: 'Project status guide EN', requireAgentTotal: true },
73
+ { file: 'docs/pt/guides/project-status-feature.md', label: 'Project status guide PT', requireAgentTotal: true },
74
+ { file: 'docs/installation/README.md', label: 'Installation index' },
75
+ { file: 'docs/installation/faq.md', label: 'Installation FAQ' },
76
+ { file: 'docs/installation/troubleshooting.md', label: 'Installation troubleshooting' },
77
+ { file: 'docs/installation/v4-quick-start.md', label: 'Installation quick start' },
78
+ { file: 'docs/pt/installation/README.md', label: 'Installation index PT' },
79
+ { file: 'docs/pt/installation/faq.md', label: 'Installation FAQ PT' },
80
+ { file: 'docs/pt/installation/troubleshooting.md', label: 'Installation troubleshooting PT' },
81
+ { file: 'docs/pt/installation/v4-quick-start.md', label: 'Installation quick start PT' },
62
82
  { file: 'package.json', label: 'package.json description', jsonField: 'description' },
63
83
  { file: 'packages/installer/src/wizard/feedback.js', label: 'wizard feedback' },
64
84
  ];
65
85
 
86
+ function collectRuntimeTaskCounts(options = {}) {
87
+ const execute = options.execFileSync || execFileSync;
88
+ const resolverPath = options.resolverPath || CODEX_RESOLVER;
89
+ const timeout = options.timeoutMs ?? 10_000;
90
+ if (!Number.isInteger(timeout) || timeout <= 0) {
91
+ throw new Error('parametric resolver timeout must be a positive integer');
92
+ }
93
+ let raw;
94
+ try {
95
+ raw = execute(process.execPath, [resolverPath, '--stats'], {
96
+ cwd: ROOT,
97
+ encoding: 'utf8',
98
+ timeout,
99
+ });
100
+ } catch (error) {
101
+ throw new Error(`cannot execute the parametric resolver: ${error.message}`);
102
+ }
103
+
104
+ let stats;
105
+ try {
106
+ stats = JSON.parse(raw);
107
+ } catch (error) {
108
+ throw new Error(`parametric resolver returned malformed JSON: ${error.message}`);
109
+ }
110
+
111
+ const fields = {
112
+ squadTasks: stats.squadTaskFiles,
113
+ developmentTasks: stats.devTaskFiles,
114
+ totalTasks: stats.totalTaskFiles,
115
+ resolvableTasks: stats.resolvableTaskPointers,
116
+ };
117
+ for (const [name, value] of Object.entries(fields)) {
118
+ if (!Number.isInteger(value) || value <= 0) {
119
+ throw new Error(`parametric resolver field ${name} must be a positive integer`);
120
+ }
121
+ }
122
+ if (fields.totalTasks !== fields.squadTasks + fields.developmentTasks) {
123
+ throw new Error('parametric resolver totalTaskFiles does not equal squadTaskFiles + devTaskFiles');
124
+ }
125
+ if (fields.resolvableTasks > fields.totalTasks) {
126
+ throw new Error('parametric resolver resolvableTaskPointers exceeds totalTaskFiles');
127
+ }
128
+ if (
129
+ options.expectedSquadTasks !== undefined &&
130
+ fields.squadTasks !== options.expectedSquadTasks
131
+ ) {
132
+ throw new Error(
133
+ `parametric resolver squadTaskFiles ${fields.squadTasks} does not match ` +
134
+ `sync-counts tasks ${options.expectedSquadTasks}`,
135
+ );
136
+ }
137
+ return fields;
138
+ }
139
+
140
+ function countHookRegistrations(settings, label) {
141
+ if (!settings || typeof settings !== 'object' || Array.isArray(settings)) {
142
+ throw new Error(`${label} hook settings must be an object`);
143
+ }
144
+ const hooks = settings.hooks;
145
+ if (!hooks || typeof hooks !== 'object' || Array.isArray(hooks)) {
146
+ throw new Error(`${label} hook settings are missing hooks`);
147
+ }
148
+ let count = 0;
149
+ for (const groups of Object.values(hooks)) {
150
+ if (!Array.isArray(groups)) throw new Error(`${label} hook groups must be arrays`);
151
+ for (const group of groups) {
152
+ if (!group || !Array.isArray(group.hooks)) {
153
+ throw new Error(`${label} hook registration is malformed`);
154
+ }
155
+ count += group.hooks.length;
156
+ }
157
+ }
158
+ if (count === 0) throw new Error(`${label} hook settings contain no registrations`);
159
+ return count;
160
+ }
161
+
162
+ function collectProviderSurfaceCounts(options = {}) {
163
+ const validateProviders = options.validateProviderAdapters || validateProviderAdapters;
164
+ const readJson = options.readJson || ((filePath) => JSON.parse(fs.readFileSync(filePath, 'utf8')));
165
+ const result = validateProviders(ROOT);
166
+ if (!result || result.ok !== true || !result.metrics) {
167
+ const detail = result && Array.isArray(result.errors) ? result.errors.join('; ') : 'invalid result';
168
+ throw new Error(`provider adapter validation failed: ${detail}`);
169
+ }
170
+ const claudeSkills = result.metrics.claudeSkills;
171
+ const codexSkills = result.metrics.codexSkills;
172
+ for (const [name, value] of Object.entries({ claudeSkills, codexSkills })) {
173
+ if (!Number.isInteger(value) || value <= 0) {
174
+ throw new Error(`provider metric ${name} must be a positive integer`);
175
+ }
176
+ }
177
+ const claudeHooks = countHookRegistrations(
178
+ readJson(path.join(ROOT, '.claude', 'settings.json')),
179
+ 'Claude',
180
+ );
181
+ const codexSettings = readJson(path.join(ROOT, '.codex', 'hooks.json'));
182
+ if (
183
+ !codexSettings ||
184
+ typeof codexSettings !== 'object' ||
185
+ !codexSettings.hooks ||
186
+ typeof codexSettings.hooks !== 'object' ||
187
+ Array.isArray(codexSettings.hooks) ||
188
+ Object.values(codexSettings.hooks).some((groups) => !Array.isArray(groups))
189
+ ) {
190
+ throw new Error('Codex hook settings are malformed');
191
+ }
192
+ const codexHookEvents = Object.keys(codexSettings.hooks).length;
193
+ if (codexHookEvents === 0) {
194
+ throw new Error('Codex hook settings contain no lifecycle events');
195
+ }
196
+ return { claudeSkills, codexSkills, claudeHooks, codexHookEvents };
197
+ }
198
+
66
199
  /**
67
200
  * Build the set of expected numeric tokens that MUST appear consistently.
68
201
  * Returns an object describing canonical counts and a regex helper.
69
202
  */
70
- function buildExpectations(counts, constitution = { articles: 0, nonNegotiable: 0 }) {
203
+ function buildExpectations(
204
+ counts,
205
+ constitution = { articles: 0, nonNegotiable: 0 },
206
+ runtimeTasks = {},
207
+ providerSurfaces = {},
208
+ ) {
71
209
  return {
72
210
  squads: counts.squads,
73
211
  agents: counts.totalAgents,
74
212
  orqx: counts.totalOrqx,
75
213
  tasks: counts.tasks,
214
+ squadTasks: runtimeTasks.squadTasks ?? counts.tasks,
215
+ developmentTasks: runtimeTasks.developmentTasks ?? 0,
216
+ totalTasks: runtimeTasks.totalTasks ?? counts.tasks,
217
+ resolvableTasks: runtimeTasks.resolvableTasks ?? 0,
218
+ claudeSkills: providerSurfaces.claudeSkills ?? 0,
219
+ codexSkills: providerSurfaces.codexSkills ?? 0,
220
+ claudeHooks: providerSurfaces.claudeHooks ?? 0,
221
+ codexHookEvents: providerSurfaces.codexHookEvents ?? 0,
76
222
  articles: constitution.articles,
77
223
  nonNegotiable: constitution.nonNegotiable,
78
224
  };
79
225
  }
80
226
 
227
+ function parseMetric(raw) {
228
+ return parseInt(String(raw).replace(/[.,]/g, ''), 10);
229
+ }
230
+
231
+ function metricPattern(value) {
232
+ const digits = String(value);
233
+ if (digits.length <= 3) return digits;
234
+ return `${digits}|${digits.slice(0, -3)}[.,]${digits.slice(-3)}`;
235
+ }
236
+
81
237
  /**
82
238
  * Extract numeric counts that look like squads/agents/orqx/tasks claims.
83
239
  * Returns drift findings (what the doc claims vs what is canonical).
84
240
  */
85
- function findDrift(content, expected) {
241
+ function findDrift(content, expected, options = {}) {
86
242
  const findings = [];
87
243
 
88
244
  // Squads: "<N> squads" or "<N> squad"
89
- const squadsRe = /\b(\d{1,4})\s+squads?\b/gi;
245
+ const squadsRe = /(?<![\d.,])(\d{1,4})\s+squads?\b/gi;
90
246
  for (const m of content.matchAll(squadsRe)) {
91
247
  const n = parseInt(m[1], 10);
92
248
  if (n !== expected.squads && n >= 5) {
@@ -96,7 +252,7 @@ function findDrift(content, expected) {
96
252
  }
97
253
 
98
254
  // Agents/agentes: "<N> agentes" or "<N> agents"
99
- const agentsRe = /\b(\d{1,4})\s+(agentes|agents)\b/gi;
255
+ const agentsRe = /(?<![\d.,])(\d{1,4})\s+(agentes|agents)\b/gi;
100
256
  for (const m of content.matchAll(agentsRe)) {
101
257
  const n = parseInt(m[1], 10);
102
258
  if (n !== expected.agents && n >= 50) {
@@ -104,13 +260,115 @@ function findDrift(content, expected) {
104
260
  }
105
261
  }
106
262
 
107
- // Tasks (com ou sem separador de milhar): "1.237 tasks", "1,237 tasks", "1237 tasks"
108
- const tasksRe = /\b(\d{1,3}(?:[.,]\d{3})?|\d{4,5})\s+tasks\b/gi;
263
+ const scopedTaskPatterns = [
264
+ { kind: 'squad tasks', expected: expected.squadTasks, re: /(?<![\d.,])(\d{1,3}(?:[.,]\d{3})?|\d{4,5})\s+squad\s+tasks\b/gi },
265
+ { kind: 'development tasks', expected: expected.developmentTasks, re: /(?<![\d.,])(\d{1,3}(?:[.,]\d{3})?|\d{4,5})\s+development\s+tasks\b/gi },
266
+ { kind: 'total task files', expected: expected.totalTasks, re: /(?<![\d.,])(\d{1,3}(?:[.,]\d{3})?|\d{4,5})\s+task\s+files\b/gi },
267
+ { kind: 'resolvable task pointers', expected: expected.resolvableTasks, re: /(?<![\d.,])(\d{1,3}(?:[.,]\d{3})?|\d{4,5})\s+(?:ponteiros|pointers)[^.\n]{0,30}resolv/gi },
268
+ ];
269
+ for (const metric of scopedTaskPatterns) {
270
+ if (!metric.expected) continue;
271
+ for (const m of content.matchAll(metric.re)) {
272
+ const n = parseMetric(m[1]);
273
+ if (n !== metric.expected) {
274
+ findings.push({ kind: metric.kind, found: n, expected: metric.expected, snippet: m[0].trim() });
275
+ }
276
+ }
277
+ }
278
+
279
+ // Generic task claims may refer to squad, development, total, or resolvable
280
+ // files. Scoped phrases above enforce the meaning of each metric.
281
+ const tasksRe = /(?<![\d.,])(\d{1,3}(?:[.,]\d{3})?|\d{4,5})\s+tasks\b/gi;
282
+ const acceptedTaskCounts = new Set([
283
+ expected.squadTasks,
284
+ expected.developmentTasks,
285
+ expected.totalTasks,
286
+ expected.resolvableTasks,
287
+ ].filter(Boolean));
109
288
  for (const m of content.matchAll(tasksRe)) {
110
- const raw = m[1].replace(/[.,]/g, '');
111
- const n = parseInt(raw, 10);
112
- if (n !== expected.tasks && n >= 100) {
113
- findings.push({ kind: 'tasks', found: n, expected: expected.tasks, snippet: m[0] });
289
+ const n = parseMetric(m[1]);
290
+ if (!acceptedTaskCounts.has(n) && n >= 100) {
291
+ findings.push({ kind: 'tasks', found: n, expected: expected.totalTasks, snippet: m[0] });
292
+ }
293
+ }
294
+
295
+ if (options.requireTaskBreakdown) {
296
+ const required = [
297
+ ['squad tasks', expected.squadTasks, 'squad\\s+tasks'],
298
+ ['development tasks', expected.developmentTasks, 'development\\s+tasks'],
299
+ ['total task files', expected.totalTasks, 'task\\s+files'],
300
+ ['resolvable task pointers', expected.resolvableTasks, '(?:ponteiros|pointers)[^.\\n]{0,30}resolv'],
301
+ ];
302
+ for (const [kind, value, suffix] of required) {
303
+ if (!value) continue;
304
+ const re = new RegExp(`\\b(?:${metricPattern(value)})\\s+${suffix}`, 'i');
305
+ if (!re.test(content)) {
306
+ findings.push({ kind: `${kind} (missing)`, found: null, expected: value, snippet: 'claim ausente' });
307
+ }
308
+ }
309
+ }
310
+
311
+ const skillsClaim = content.match(/(?:Skills\s+instaladas|Installed\s+skills)\s*\|\s*(\d+)\s*\|\s*(\d+)/i);
312
+ if (skillsClaim) {
313
+ for (const [kind, found, wanted] of [
314
+ ['Claude skills', Number(skillsClaim[1]), expected.claudeSkills],
315
+ ['Codex skills', Number(skillsClaim[2]), expected.codexSkills],
316
+ ]) {
317
+ if (wanted && found !== wanted) findings.push({ kind, found, expected: wanted, snippet: skillsClaim[0] });
318
+ }
319
+ }
320
+ const hooksClaim = content.match(/(?:Hooks\s+registrados|Registered\s+hooks)\s*\|\s*(\d+)\s+(?:registros|native\s+registrations)[^|]*\|\s*(\d+)\s+(?:eventos|lifecycle\s+events)/i);
321
+ if (hooksClaim) {
322
+ for (const [kind, found, wanted] of [
323
+ ['Claude hook registrations', Number(hooksClaim[1]), expected.claudeHooks],
324
+ ['Codex hook events', Number(hooksClaim[2]), expected.codexHookEvents],
325
+ ]) {
326
+ if (wanted && found !== wanted) findings.push({ kind, found, expected: wanted, snippet: hooksClaim[0] });
327
+ }
328
+ }
329
+ const hookProseRe = /(?<![\d.,])(\d{1,3})\s+(?:Claude\s+Code\s+hook\s+registrations?|hooks?\s+(?:registrados?|ativos?)|(?:(?:active|registered|enforcement)\s+)(?:(?:Claude(?:\s+Code)?|native(?:\s+Claude(?:\s+Code)?)?)\s+)?hooks|hooks)\b/gi;
330
+ for (const claim of content.matchAll(hookProseRe)) {
331
+ const found = Number(claim[1]);
332
+ if (expected.claudeHooks && found !== expected.claudeHooks) {
333
+ findings.push({
334
+ kind: 'Claude hook registrations',
335
+ found,
336
+ expected: expected.claudeHooks,
337
+ snippet: claim[0],
338
+ });
339
+ }
340
+ }
341
+ const codexLifecycleProseRe = /(?<![\d.,])(\d{1,3})\s+(?:(?:Codex\s+)?lifecycle\s+events?|eventos\s+de\s+lifecycle)\b/gi;
342
+ for (const claim of content.matchAll(codexLifecycleProseRe)) {
343
+ const found = Number(claim[1]);
344
+ if (expected.codexHookEvents && found !== expected.codexHookEvents) {
345
+ findings.push({
346
+ kind: 'Codex hook events',
347
+ found,
348
+ expected: expected.codexHookEvents,
349
+ snippet: claim[0],
350
+ });
351
+ }
352
+ }
353
+ if (options.requireProviderBreakdown) {
354
+ if (!skillsClaim) findings.push({ kind: 'provider skills (missing)', found: null, expected: `${expected.claudeSkills}/${expected.codexSkills}`, snippet: 'claim ausente' });
355
+ if (!hooksClaim) findings.push({ kind: 'provider hooks (missing)', found: null, expected: `${expected.claudeHooks}/${expected.codexHookEvents}`, snippet: 'claim ausente' });
356
+ }
357
+
358
+ if (options.requireAgentTotal) {
359
+ const expectedAgentPattern = metricPattern(expected.agents);
360
+ const agentClaim = new RegExp(
361
+ '(?:SINAPSE\\s+(?:includes|inclui)|O\\s+SINAPSE\\s+inclui)[^.\\n]{0,40}' +
362
+ `\\b(?:${expectedAgentPattern})\\s+(?:specialized\\s+agents|agentes\\s+especializados)`,
363
+ 'i',
364
+ );
365
+ if (!agentClaim.test(content)) {
366
+ findings.push({
367
+ kind: 'global agent total (missing)',
368
+ found: null,
369
+ expected: expected.agents,
370
+ snippet: 'claim ausente',
371
+ });
114
372
  }
115
373
  }
116
374
 
@@ -171,14 +429,28 @@ function main() {
171
429
 
172
430
  const counts = collectCounts();
173
431
  const constitution = collectConstitutionCounts();
174
- const expected = buildExpectations(counts, constitution);
432
+ let runtimeTasks;
433
+ let providerSurfaces;
434
+ try {
435
+ runtimeTasks = collectRuntimeTaskCounts({ expectedSquadTasks: counts.tasks });
436
+ providerSurfaces = collectProviderSurfaceCounts();
437
+ } catch (error) {
438
+ console.error(`FALHA - nao foi possivel medir as metricas canonicas: ${error.message}`);
439
+ process.exit(1);
440
+ }
441
+ const expected = buildExpectations(counts, constitution, runtimeTasks, providerSurfaces);
175
442
 
176
443
  console.log('Article VII (Metrics Accuracy) — validacao de drift');
177
- console.log('Numeros canonicos (de .sinapse-ai/constitution.md):');
444
+ console.log('Numeros canonicos (medidos das fontes em disco):');
178
445
  console.log(` squads ${expected.squads}`);
179
446
  console.log(` agentes ${expected.agents}`);
180
447
  console.log(` orqx ${expected.orqx}`);
181
- console.log(` tasks ${expected.tasks}`);
448
+ console.log(` squad tasks ${expected.squadTasks}`);
449
+ console.log(` dev tasks ${expected.developmentTasks}`);
450
+ console.log(` task files ${expected.totalTasks}`);
451
+ console.log(` resolvable ${expected.resolvableTasks}`);
452
+ console.log(` skills C/Codex ${expected.claudeSkills}/${expected.codexSkills}`);
453
+ console.log(` hooks C/Codex ${expected.claudeHooks}/${expected.codexHookEvents}`);
182
454
  console.log(` artigos ${expected.articles}`);
183
455
  console.log(` NON-NEGOTIABLE ${expected.nonNegotiable}`);
184
456
  console.log('');
@@ -191,7 +463,12 @@ function main() {
191
463
  console.log(` [skip] ${target.file} nao encontrado`);
192
464
  continue;
193
465
  }
194
- const drift = findDrift(content, expected);
466
+ const drift = findDrift(content, expected, {
467
+ requireTaskBreakdown: target.requireTaskBreakdown === true,
468
+ requireProviderBreakdown:
469
+ target.requireTaskBreakdown === true || target.requireProviderBreakdown === true,
470
+ requireAgentTotal: target.requireAgentTotal === true,
471
+ });
195
472
  if (drift.length === 0) {
196
473
  console.log(` [ok] ${target.file}`);
197
474
  } else {
@@ -214,7 +491,7 @@ function main() {
214
491
  console.log('');
215
492
  console.log('Acao corretiva (Article VII NON-NEGOTIABLE):');
216
493
  console.log(' 1. Atualize cada arquivo listado acima com os numeros canonicos.');
217
- console.log(' 2. Rode `npm run sync:counts` para regenerar a constitution.');
494
+ console.log(' 2. Se o inventario mudou, rode `npm run sync:counts`.');
218
495
  console.log(' 3. Rode `npm run validate:article-vii` novamente para confirmar.');
219
496
 
220
497
  if (fixMode) {
@@ -234,4 +511,12 @@ if (require.main === module) {
234
511
  main();
235
512
  }
236
513
 
237
- module.exports = { findDrift, buildExpectations, collectConstitutionCounts, TARGETS };
514
+ module.exports = {
515
+ findDrift,
516
+ buildExpectations,
517
+ collectConstitutionCounts,
518
+ collectRuntimeTaskCounts,
519
+ collectProviderSurfaceCounts,
520
+ countHookRegistrations,
521
+ TARGETS,
522
+ };