sinapse-ai 1.25.3 → 1.27.0

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.
@@ -50,6 +50,10 @@ const {
50
50
  } = require('../lib/command-generator');
51
51
  const { deliverGlobalProviderAdapters, getGlobalCommandStagingDir } = require('../lib/global-provider-adapters');
52
52
  const { assertProviderAdapterParity } = require('../lib/provider-parity');
53
+ const {
54
+ copyReactBitsCapabilitySync,
55
+ REACT_BITS_SKILL_RELATIVE_PATH,
56
+ } = require('../../packages/installer/src/installer/react-bits-corpus');
53
57
  // Follow-up #13 — wire the transactional backup/rollback engine into the
54
58
  // installer so an in-place UPGRADE (upsert) that fails mid-flight is restored
55
59
  // to its previous state instead of leaving ~/.sinapse half-updated.
@@ -130,17 +134,18 @@ async function cmdInstallGlobal(opts = {}) {
130
134
 
131
135
  // LLM selection (skipped in upsert mode if previous llm known)
132
136
  // Story 10.35: --reconfigure forces prompt even in upsert mode
133
- let llmChoice;
134
- let llmWasReused = false;
135
- if (requestedLlm) {
136
- llmChoice = requestedLlm;
137
- } else if (isUpsert && !reconfigure && existing.llm) {
138
- llmChoice = existing.llm;
139
- llmWasReused = true;
137
+ const llmChoice = await resolveLlmChoice({
138
+ requestedLlm,
139
+ isUpsert,
140
+ reconfigure,
141
+ existingLlm: existing.llm,
142
+ });
143
+ const llmWasReused = Boolean(isUpsert && !reconfigure && !requestedLlm && existing.llm);
144
+ if (llmWasReused) {
140
145
  const ideLabel = Array.isArray(llmChoice) ? llmChoice.join(', ') : String(llmChoice);
141
146
  logger.always(`${DIM} IDE: ${ideLabel} (from saved config; pass --reconfigure to change)${NC}`);
142
- } else {
143
- llmChoice = await promptLlmChoice();
147
+ } else if (!requestedLlm && !reconfigure) {
148
+ logger.always(`${DIM} IDE: Claude Code + Codex (padrao; use --llm para limitar)${NC}`);
144
149
  }
145
150
  if (languageWasReused || llmWasReused) logger.always('');
146
151
 
@@ -317,7 +322,12 @@ async function cmdInstallGlobal(opts = {}) {
317
322
  // rich, frontmatter'd stubs are always the final word. Idempotent.
318
323
  try {
319
324
  const commandsDir = getGlobalCommandStagingDir({ llmChoice, sinapseHome: SINAPSE_HOME, claudeCommandsDir: CLAUDE_COMMANDS_DIR });
320
- const reconciled = deliverGlobalProviderAdapters({ llmChoice, home: HOME, commandsDir });
325
+ const reconciled = deliverGlobalProviderAdapters({
326
+ llmChoice,
327
+ home: HOME,
328
+ commandsDir,
329
+ reactBitsSkillPath: path.join(SINAPSE_HOME, REACT_BITS_SKILL_RELATIVE_PATH),
330
+ });
321
331
  const total = reconciled.claude.length + reconciled.codex.length + reconciled.skills.length;
322
332
  if (total) logger.always(` ${GREEN}OK${NC} Global provider adapters reconciled (${total} artifacts)`);
323
333
  } catch (e) {
@@ -373,6 +383,24 @@ async function cmdInstallGlobal(opts = {}) {
373
383
  logger.always('');
374
384
  }
375
385
 
386
+ /**
387
+ * Resolves provider selection independently of filesystem writes and prompts.
388
+ * Explicit flags win; existing configured installs remain stable until the
389
+ * user reconfigures them; fresh or legacy-unconfigured installs use both.
390
+ */
391
+ async function resolveLlmChoice({
392
+ requestedLlm = null,
393
+ isUpsert = false,
394
+ reconfigure = false,
395
+ existingLlm = null,
396
+ prompt = promptLlmChoice,
397
+ } = {}) {
398
+ if (requestedLlm) return requestedLlm;
399
+ if (isUpsert && !reconfigure && existingLlm) return existingLlm;
400
+ if (reconfigure) return prompt();
401
+ return 'both';
402
+ }
403
+
376
404
  // ── Transactional install helpers (follow-up #13) ────────────────────────────
377
405
 
378
406
  /**
@@ -557,6 +585,9 @@ function installFatalPhases({ squads, squadsDir, isUpsert, llmChoice, existing,
557
585
  logger.always(` ${GREEN}OK${NC} core development (${coreAgents} agents)`);
558
586
  }
559
587
 
588
+ const reactBits = copyReactBitsCapabilitySync(ROOT, SINAPSE_HOME);
589
+ if (reactBits.files.length) logger.always(` ${GREEN}OK${NC} React Bits corpus (${reactBits.files.length} files)`);
590
+
560
591
  // Phase 2: Generate agent commands (shared with `update` via command-generator).
561
592
  logger.always(`\n${CYAN}Phase 2:${NC} Generating agent commands`);
562
593
  const commandStagingDir = getGlobalCommandStagingDir({ llmChoice, sinapseHome: SINAPSE_HOME, claudeCommandsDir: CLAUDE_COMMANDS_DIR });
@@ -570,7 +601,12 @@ function installFatalPhases({ squads, squadsDir, isUpsert, llmChoice, existing,
570
601
  logger.always(` ${GREEN}OK${NC} ${writtenAgents.size} total command files`);
571
602
 
572
603
  // Phase 2b: Install global agents based on LLM choice
573
- const globalAdapters = deliverGlobalProviderAdapters({ llmChoice, home: HOME, commandsDir: commandStagingDir });
604
+ const globalAdapters = deliverGlobalProviderAdapters({
605
+ llmChoice,
606
+ home: HOME,
607
+ commandsDir: commandStagingDir,
608
+ reactBitsSkillPath: path.join(SINAPSE_HOME, REACT_BITS_SKILL_RELATIVE_PATH),
609
+ });
574
610
  const activeAgentCount = assertProviderAdapterParity(
575
611
  llmChoice,
576
612
  globalAdapters,
@@ -854,6 +890,7 @@ function ensurePathWindows() {
854
890
 
855
891
  module.exports = {
856
892
  cmdInstallGlobal,
893
+ resolveLlmChoice,
857
894
  generateCommandMd,
858
895
  generateSquadAwareness,
859
896
  createLauncher,
@@ -170,7 +170,7 @@ function removeOrqxAgentsFrom(dir) {
170
170
  return removeInstalledAgentsFrom(dir);
171
171
  }
172
172
 
173
- const MANAGED_GLOBAL_SKILL_IDS = ['snps', 'sinapse', 'snps-orqx', 'sinapse-orqx', 'sinapse-agent'];
173
+ const MANAGED_GLOBAL_SKILL_IDS = ['snps', 'sinapse', 'snps-orqx', 'sinapse-orqx', 'sinapse-agent', 'react-bits-frontend'];
174
174
  const MANAGED_GLOBAL_SKILL_MARKER = 'SINAPSE-MANAGED:global-skill';
175
175
 
176
176
  function removeManagedGlobalSkills(home = HOME, options = {}) {
@@ -27,7 +27,6 @@ const {
27
27
  detectExistingInstall,
28
28
  detectStaleness,
29
29
  } = require('../lib/detection');
30
- const { promptLlmChoice } = require('../lib/prompts');
31
30
  const { generateSquadAwareness } = require('./install');
32
31
  const {
33
32
  recordInstalledAgents,
@@ -36,6 +35,10 @@ const {
36
35
  } = require('./uninstall');
37
36
  const { regenerateAgentCommands } = require('../lib/command-generator');
38
37
  const { deliverGlobalProviderAdapters, getGlobalCommandStagingDir } = require('../lib/global-provider-adapters');
38
+ const {
39
+ copyReactBitsCapabilitySync,
40
+ REACT_BITS_SKILL_RELATIVE_PATH,
41
+ } = require('../../packages/installer/src/installer/react-bits-corpus');
39
42
  const { assertProviderAdapterParity } = require('../lib/provider-parity');
40
43
  const { execSync } = require('child_process');
41
44
 
@@ -118,9 +121,12 @@ async function cmdUpdateGlobal() {
118
121
  logger.always('');
119
122
  }
120
123
 
121
- // Story 10.22 — skip LLM prompt when previous llm known. To re-prompt,
122
- // run `npx sinapse-ai install --force`.
123
- const llmChoice = existing.llm || await promptLlmChoice();
124
+ // Story 10.22 — preserve the saved provider selection. To choose again,
125
+ // run `npx sinapse-ai@latest install --reconfigure`.
126
+ const llmChoice = existing.llm || 'both';
127
+ if (!existing.llm) {
128
+ logger.always(`${DIM} IDE: Claude Code + Codex (padrao para instalacoes legadas sem provider salvo)${NC}`);
129
+ }
124
130
 
125
131
  logger.always('');
126
132
  logger.always(`${BOLD}Atualizando SINAPSE AI...${NC}\n`);
@@ -164,6 +170,9 @@ async function cmdUpdateGlobal() {
164
170
  logger.always(` ${GREEN}OK${NC} core development`);
165
171
  }
166
172
 
173
+ const reactBits = copyReactBitsCapabilitySync(ROOT, SINAPSE_HOME);
174
+ if (reactBits.files.length) logger.always(` ${GREEN}OK${NC} React Bits corpus (${reactBits.files.length} files)`);
175
+
167
176
  // Phase 2: Regenerate commands — shared with `install` so `update` produces the
168
177
  // SAME complete set (every specialist command + the rich master Imperator stubs),
169
178
  // not just the `-orqx` subset it used to write.
@@ -180,7 +189,12 @@ async function cmdUpdateGlobal() {
180
189
  logger.always(` ${GREEN}OK${NC} ${writtenAgents.size} command files (${totalAgents} agents total)`);
181
190
 
182
191
  // Phase 2b: Install global agents based on LLM choice
183
- const globalAdapters = deliverGlobalProviderAdapters({ llmChoice, home: HOME, commandsDir: commandStagingDir });
192
+ const globalAdapters = deliverGlobalProviderAdapters({
193
+ llmChoice,
194
+ home: HOME,
195
+ commandsDir: commandStagingDir,
196
+ reactBitsSkillPath: path.join(SINAPSE_HOME, REACT_BITS_SKILL_RELATIVE_PATH),
197
+ });
184
198
  const activeAgentCount = assertProviderAdapterParity(
185
199
  llmChoice,
186
200
  globalAdapters,
@@ -82,7 +82,7 @@ function warnNonInteractive() {
82
82
  nonInteractiveWarned = true;
83
83
  // stderr so it never pollutes stdout consumers (CI logs, pipes).
84
84
  process.stderr.write(
85
- `${YELLOW}INFO${NC} ambiente nao-interativo detectado, usando defaults pt + claude-code ` +
85
+ `${YELLOW}INFO${NC} ambiente nao-interativo detectado, usando defaults pt + Claude Code + Codex ` +
86
86
  `${DIM}(use --interactive pra forcar prompts)${NC}\n`,
87
87
  );
88
88
  }
@@ -6,6 +6,7 @@ const crypto = require('crypto');
6
6
  const {
7
7
  MASTER_ALIAS_ENTRY_POINTS,
8
8
  GLOBAL_PROVIDER_SKILL_IDS,
9
+ GLOBAL_SUPPLEMENTAL_PROVIDER_SKILL_IDS,
9
10
  SUPREME_ORCHESTRATOR_ID,
10
11
  SUPREME_PUBLIC_ID,
11
12
  } = require('./provider-contract');
@@ -102,7 +103,11 @@ function markGlobalAgent(markdown, fileName = '') {
102
103
  return `${content.trimEnd()}\n\n<!-- SINAPSE-MANAGED:global-agent -->\n`;
103
104
  }
104
105
 
105
- function renderGlobalSkill(skillId) {
106
+ function renderGlobalSkill(skillId, sourceContent = null) {
107
+ if (skillId === 'react-bits-frontend') {
108
+ if (!sourceContent) throw new Error('React Bits global skill source is required');
109
+ return `${String(sourceContent).trimEnd()}\n\n<!-- SINAPSE-MANAGED:global-skill -->\n`;
110
+ }
106
111
  const generic = skillId === 'sinapse-agent';
107
112
  return [
108
113
  '---',
@@ -264,9 +269,9 @@ function writeGlobalSkillIfManaged(skillPath, content, home = path.dirname(skill
264
269
  }
265
270
  }
266
271
 
267
- function isValidGlobalSkill(skillId, skillPath) {
272
+ function isValidGlobalSkill(skillId, skillPath, sourceContent = null) {
268
273
  const content = readRegularFileNoFollowSync(skillPath, 'utf8');
269
- return content !== null && content === renderGlobalSkill(skillId);
274
+ return content !== null && content === renderGlobalSkill(skillId, sourceContent);
270
275
  }
271
276
 
272
277
  function removeStaleManagedAgents(targetDir, expectedFiles, extension, options = {}) {
@@ -318,7 +323,7 @@ function removeManagedFileWithBinding(home, filePath, beforeDelete) {
318
323
  }
319
324
  }
320
325
 
321
- function deliverGlobalProviderAdapters({ llmChoice, home, commandsDir, testHooks = {} }) {
326
+ function deliverGlobalProviderAdapters({ llmChoice, home, commandsDir, reactBitsSkillPath, testHooks = {} }) {
322
327
  let commandFiles = fs.existsSync(commandsDir)
323
328
  ? fs.readdirSync(commandsDir).filter((file) => file.endsWith('.md')).sort()
324
329
  : [];
@@ -334,6 +339,13 @@ function deliverGlobalProviderAdapters({ llmChoice, home, commandsDir, testHooks
334
339
  skills: [],
335
340
  availableSkills: [],
336
341
  };
342
+ let reactBitsSkillContent = null;
343
+ const providerSkillIds = [...GLOBAL_PROVIDER_SKILL_IDS];
344
+ if (reactBitsSkillPath) {
345
+ reactBitsSkillContent = readRegularFileNoFollowSync(reactBitsSkillPath, 'utf8');
346
+ if (reactBitsSkillContent === null) throw new Error(`React Bits global skill is missing: ${reactBitsSkillPath}`);
347
+ providerSkillIds.push(...GLOBAL_SUPPLEMENTAL_PROVIDER_SKILL_IDS);
348
+ }
337
349
 
338
350
  if (llmChoice === 'claude-code' || llmChoice === 'both') {
339
351
  const targetDir = path.join(home, '.claude', 'agents');
@@ -345,12 +357,12 @@ function deliverGlobalProviderAdapters({ llmChoice, home, commandsDir, testHooks
345
357
  written.claude.push(file);
346
358
  }
347
359
  const skillsRoot = path.join(home, '.claude', 'skills');
348
- for (const skillId of GLOBAL_PROVIDER_SKILL_IDS) {
360
+ for (const skillId of providerSkillIds) {
349
361
  const skillPath = path.join(skillsRoot, skillId, 'SKILL.md');
350
- if (writeGlobalSkillIfManaged(skillPath, renderGlobalSkill(skillId), home, { beforePublish: testHooks.beforeSkillPublish })) {
362
+ if (writeGlobalSkillIfManaged(skillPath, renderGlobalSkill(skillId, reactBitsSkillContent), home, { beforePublish: testHooks.beforeSkillPublish })) {
351
363
  written.claudeSkills.push(skillId);
352
364
  }
353
- if (isValidGlobalSkill(skillId, skillPath)) written.claudeAvailableSkills.push(skillId);
365
+ if (isValidGlobalSkill(skillId, skillPath, reactBitsSkillContent)) written.claudeAvailableSkills.push(skillId);
354
366
  }
355
367
  }
356
368
  if (llmChoice === 'codex' || llmChoice === 'both') {
@@ -373,13 +385,13 @@ function deliverGlobalProviderAdapters({ llmChoice, home, commandsDir, testHooks
373
385
  written.codex.push(tomlName);
374
386
  }
375
387
  const skillsRoot = path.join(home, '.agents', 'skills');
376
- for (const skillId of GLOBAL_PROVIDER_SKILL_IDS) {
388
+ for (const skillId of providerSkillIds) {
377
389
  const skillDir = path.join(skillsRoot, skillId);
378
390
  const skillPath = path.join(skillDir, 'SKILL.md');
379
- if (writeGlobalSkillIfManaged(skillPath, renderGlobalSkill(skillId), home, { beforePublish: testHooks.beforeSkillPublish })) {
391
+ if (writeGlobalSkillIfManaged(skillPath, renderGlobalSkill(skillId, reactBitsSkillContent), home, { beforePublish: testHooks.beforeSkillPublish })) {
380
392
  written.skills.push(skillId);
381
393
  }
382
- if (isValidGlobalSkill(skillId, skillPath)) written.availableSkills.push(skillId);
394
+ if (isValidGlobalSkill(skillId, skillPath, reactBitsSkillContent)) written.availableSkills.push(skillId);
383
395
  }
384
396
  }
385
397
  return written;
@@ -15,7 +15,7 @@ async function promptLlmChoice() {
15
15
  // check that silently skipped this prompt in Git Bash + Windows.
16
16
  if (!detectInteractiveMode()) {
17
17
  warnNonInteractive();
18
- return 'claude-code';
18
+ return 'both';
19
19
  }
20
20
  try {
21
21
  const inquirer = require('inquirer');
@@ -24,11 +24,11 @@ async function promptLlmChoice() {
24
24
  name: 'llms',
25
25
  message: 'Escolha sua LLM (espaco seleciona, enter confirma):',
26
26
  choices: [
27
- { name: 'Claude Code (Recomendado)', value: 'claude-code', checked: true },
28
- { name: 'Codex CLI', value: 'codex' },
27
+ { name: 'Claude Code', value: 'claude-code', checked: true },
28
+ { name: 'Codex CLI', value: 'codex', checked: true },
29
29
  ],
30
30
  }]);
31
- if (llms.length === 0) return 'claude-code'; // default if none selected
31
+ if (llms.length === 0) return 'both'; // default if none selected
32
32
  if (llms.length === 2) return 'both';
33
33
  return llms[0];
34
34
  } catch {
@@ -37,16 +37,16 @@ async function promptLlmChoice() {
37
37
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
38
38
  return new Promise((resolve) => {
39
39
  logger.always(`${CYAN} Escolha sua LLM:${NC}`);
40
- logger.always(` ${GREEN}1${NC}) Claude Code ${DIM}(Recomendado)${NC}`);
40
+ logger.always(` ${GREEN}1${NC}) Claude Code`);
41
41
  logger.always(` ${GREEN}2${NC}) Codex CLI`);
42
- logger.always(` ${GREEN}3${NC}) Ambos`);
42
+ logger.always(` ${GREEN}3${NC}) Ambos ${DIM}(Recomendado)${NC}`);
43
43
  logger.always('');
44
44
  rl.question(` ${BOLD}Opcao [1/2/3]:${NC} `, (answer) => {
45
45
  rl.close();
46
- const choice = (answer || '1').trim();
46
+ const choice = (answer || '3').trim();
47
47
  if (choice === '2') resolve('codex');
48
- else if (choice === '3') resolve('both');
49
- else resolve('claude-code');
48
+ else if (choice === '1') resolve('claude-code');
49
+ else resolve('both');
50
50
  });
51
51
  });
52
52
  }
@@ -13,6 +13,7 @@ const GLOBAL_PROVIDER_SKILL_IDS = Object.freeze([
13
13
  SUPREME_ORCHESTRATOR_ID,
14
14
  'sinapse-agent',
15
15
  ]);
16
+ const GLOBAL_SUPPLEMENTAL_PROVIDER_SKILL_IDS = Object.freeze(['react-bits-frontend']);
16
17
 
17
18
  module.exports = {
18
19
  CANONICAL_AGENT_COUNT,
@@ -20,4 +21,5 @@ module.exports = {
20
21
  SUPREME_PUBLIC_ID,
21
22
  MASTER_ALIAS_ENTRY_POINTS,
22
23
  GLOBAL_PROVIDER_SKILL_IDS,
24
+ GLOBAL_SUPPLEMENTAL_PROVIDER_SKILL_IDS,
23
25
  };
@@ -3,8 +3,14 @@
3
3
  const {
4
4
  CANONICAL_AGENT_COUNT,
5
5
  GLOBAL_PROVIDER_SKILL_IDS,
6
+ GLOBAL_SUPPLEMENTAL_PROVIDER_SKILL_IDS,
6
7
  } = require('./provider-contract');
7
8
 
9
+ const REQUIRED_GLOBAL_PROVIDER_SKILL_IDS = Object.freeze([
10
+ ...GLOBAL_PROVIDER_SKILL_IDS,
11
+ ...GLOBAL_SUPPLEMENTAL_PROVIDER_SKILL_IDS,
12
+ ]);
13
+
8
14
  function normalizeCanonicalCatalog(canonicalCatalog, extension) {
9
15
  if (!canonicalCatalog || typeof canonicalCatalog === 'number') {
10
16
  throw new Error('Provider adapter parity requires the canonical agent ID catalog');
@@ -55,14 +61,14 @@ function assertProviderAdapterParity(llmChoice, adapters, canonicalCatalog) {
55
61
  if (llmChoice === 'claude-code' || llmChoice === 'both') {
56
62
  problems.push(...describeCatalogDivergence('Claude Code', adapters.claude, claudeCatalog));
57
63
  const claudeSkills = new Set(adapters.claudeAvailableSkills || adapters.claudeSkills || []);
58
- const missingAliases = GLOBAL_PROVIDER_SKILL_IDS.filter((alias) => !claudeSkills.has(alias));
59
- if (missingAliases.length) problems.push(`Claude Code alias skills missing: ${missingAliases.join(', ')}`);
64
+ const missingSkills = REQUIRED_GLOBAL_PROVIDER_SKILL_IDS.filter((skillId) => !claudeSkills.has(skillId));
65
+ if (missingSkills.length) problems.push(`Claude Code global skills missing: ${missingSkills.join(', ')}`);
60
66
  }
61
67
  if (llmChoice === 'codex' || llmChoice === 'both') {
62
68
  problems.push(...describeCatalogDivergence('Codex', adapters.codex, codexCatalog));
63
69
  const codexSkills = new Set(adapters.availableSkills || adapters.skills || []);
64
- const missingAliases = GLOBAL_PROVIDER_SKILL_IDS.filter((alias) => !codexSkills.has(alias));
65
- if (missingAliases.length) problems.push(`Codex alias skills missing: ${missingAliases.join(', ')}`);
70
+ const missingSkills = REQUIRED_GLOBAL_PROVIDER_SKILL_IDS.filter((skillId) => !codexSkills.has(skillId));
71
+ if (missingSkills.length) problems.push(`Codex global skills missing: ${missingSkills.join(', ')}`);
66
72
  }
67
73
  if (problems.length) {
68
74
  throw new Error(`Provider adapter parity failed (${problems.join('; ')})`);
@@ -70,4 +76,4 @@ function assertProviderAdapterParity(llmChoice, adapters, canonicalCatalog) {
70
76
  return claudeCatalog.count;
71
77
  }
72
78
 
73
- module.exports = { assertProviderAdapterParity };
79
+ module.exports = { assertProviderAdapterParity, REQUIRED_GLOBAL_PROVIDER_SKILL_IDS };
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## Pré-requisitos
6
6
 
7
- - **Node.js** ≥ 20 (LTS recomendado)
7
+ - **Node.js** ≥ 18 (Node 22 LTS recomendado)
8
8
  - **Claude Code** ou **Codex CLI** instalado
9
9
  - **Git** (pra colaboração)
10
10
 
@@ -14,7 +14,7 @@
14
14
  npx sinapse-ai@latest install
15
15
  ```
16
16
 
17
- O wizard detecta seu ambiente, escolhe IDE (Claude Code ou Codex), instala os 17 squads e configura os hooks essenciais automaticamente.
17
+ O wizard detecta seu ambiente e, em instalações novas ou sem provider salvo, instala Claude Code e Codex por padrão, além dos 17 squads e hooks essenciais. Instalações existentes preservam o provider salvo; use `--reconfigure` para alterá-lo. Use `--llm=claude-code` ou `--llm=codex` somente quando quiser limitar a instalação a um provider.
18
18
 
19
19
  ## Validar setup
20
20
 
@@ -32,7 +32,7 @@ Both providers expose native lifecycle surfaces. Their registration models diffe
32
32
  | Measured surface | Claude Code | Codex CLI |
33
33
  | --- | --- | --- |
34
34
  | Native agents | 172 | 172 |
35
- | Installed skills | 36 | 37 |
35
+ | Installed skills | 37 | 37 |
36
36
  | Registered hooks | 20 native registrations | 9 lifecycle events through the bridge |
37
37
  | Activation | `@agent-name` | `$snps` or `$sinapse-agent <id>` |
38
38
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sinapse-ai",
3
- "version": "1.25.3",
3
+ "version": "1.27.0",
4
4
  "description": "SINAPSE AI: Framework de orquestracao de IA — 17 squads, 172 agentes especializados",
5
5
  "bin": {
6
6
  "sinapse": "bin/sinapse.js",
@@ -0,0 +1,90 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const REACT_BITS_CORPUS_RELATIVE_PATH = path.join('docs', 'framework', 'react-bits');
7
+ const REACT_BITS_SKILL_RELATIVE_PATH = path.join('.agents', 'skills', 'react-bits-frontend', 'SKILL.md');
8
+ const REQUIRED_REACT_BITS_CORPUS_FILES = Object.freeze([
9
+ 'animations.md',
10
+ 'audit-findings.md',
11
+ 'backgrounds.md',
12
+ 'catalog-summary.json',
13
+ 'components.md',
14
+ 'implementation-playbook.md',
15
+ 'index.md',
16
+ 'inventory.json',
17
+ 'text-animations.md',
18
+ ]);
19
+
20
+ function isRegularFile(filePath) {
21
+ try {
22
+ return fs.lstatSync(filePath).isFile();
23
+ } catch (error) {
24
+ if (error.code === 'ENOENT') return false;
25
+ throw error;
26
+ }
27
+ }
28
+
29
+ function listFilesRecursively(directory, root = directory) {
30
+ if (!fs.existsSync(directory)) return [];
31
+ return fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
32
+ const sourcePath = path.join(directory, entry.name);
33
+ if (entry.isDirectory()) return listFilesRecursively(sourcePath, root);
34
+ return isRegularFile(sourcePath) ? [path.relative(root, sourcePath)] : [];
35
+ });
36
+ }
37
+
38
+ function getMissingReactBitsCorpusFiles(root) {
39
+ return REQUIRED_REACT_BITS_CORPUS_FILES.filter((file) => !isRegularFile(
40
+ path.join(root, REACT_BITS_CORPUS_RELATIVE_PATH, file),
41
+ ));
42
+ }
43
+
44
+ /**
45
+ * Copy only the shipped React Bits corpus. Existing destination-only files are
46
+ * intentionally left untouched so a project or global installation never
47
+ * deletes user research alongside the managed corpus.
48
+ */
49
+ function copyReactBitsCorpusSync(sourceRoot, destinationRoot) {
50
+ const source = path.join(sourceRoot, REACT_BITS_CORPUS_RELATIVE_PATH);
51
+ const destination = path.join(destinationRoot, REACT_BITS_CORPUS_RELATIVE_PATH);
52
+ const missingFiles = getMissingReactBitsCorpusFiles(sourceRoot);
53
+ if (missingFiles.length > 0) {
54
+ throw new Error(`Incomplete React Bits corpus source: ${missingFiles.join(', ')}`);
55
+ }
56
+ const files = listFilesRecursively(source);
57
+ for (const relativePath of files) {
58
+ const target = path.join(destination, relativePath);
59
+ fs.mkdirSync(path.dirname(target), { recursive: true });
60
+ fs.copyFileSync(path.join(source, relativePath), target);
61
+ }
62
+ return files.map((relativePath) => path.join(REACT_BITS_CORPUS_RELATIVE_PATH, relativePath));
63
+ }
64
+
65
+ function hasCompleteReactBitsCorpus(root) {
66
+ return getMissingReactBitsCorpusFiles(root).length === 0;
67
+ }
68
+
69
+ /** Copy the corpus plus its canonical skill into a global SINAPSE home. */
70
+ function copyReactBitsCapabilitySync(sourceRoot, destinationRoot) {
71
+ const files = copyReactBitsCorpusSync(sourceRoot, destinationRoot);
72
+ const sourceSkill = path.join(sourceRoot, REACT_BITS_SKILL_RELATIVE_PATH);
73
+ const destinationSkill = path.join(destinationRoot, REACT_BITS_SKILL_RELATIVE_PATH);
74
+ if (!fs.existsSync(sourceSkill)) throw new Error(`Missing React Bits skill: ${sourceSkill}`);
75
+ fs.mkdirSync(path.dirname(destinationSkill), { recursive: true });
76
+ fs.copyFileSync(sourceSkill, destinationSkill);
77
+ return { files, skillPath: destinationSkill };
78
+ }
79
+
80
+ module.exports = {
81
+ REACT_BITS_CORPUS_RELATIVE_PATH,
82
+ REACT_BITS_SKILL_RELATIVE_PATH,
83
+ REQUIRED_REACT_BITS_CORPUS_FILES,
84
+ isRegularFile,
85
+ listFilesRecursively,
86
+ getMissingReactBitsCorpusFiles,
87
+ copyReactBitsCorpusSync,
88
+ copyReactBitsCapabilitySync,
89
+ hasCompleteReactBitsCorpus,
90
+ };
@@ -14,6 +14,7 @@ const path = require('path');
14
14
  const crypto = require('crypto');
15
15
  const ora = require('ora');
16
16
  const { hashFile } = require('./file-hasher');
17
+ const { copyReactBitsCorpusSync } = require('./react-bits-corpus');
17
18
 
18
19
  const NOFOLLOW_READ_FLAGS = nativeFs.constants.O_RDONLY | (nativeFs.constants.O_NOFOLLOW || 0);
19
20
 
@@ -705,6 +706,13 @@ async function installSinapseCore(options = {}) {
705
706
  if (claudeSettings) result.installedFiles.push(claudeSettings);
706
707
  }
707
708
 
709
+ if (includeClaude || includeCodex) {
710
+ spinner.text = 'Copying React Bits corpus...';
711
+ const corpusFiles = copyReactBitsCorpusSync(pkgRoot, targetDir);
712
+ result.reactBitsCorpusFiles = corpusFiles.length;
713
+ result.installedFiles.push(...corpusFiles);
714
+ }
715
+
708
716
  // AGENTS.md is the Codex CLI's default context file (Imperator greeting +
709
717
  // the full agent roster). It lives at the package ROOT (not under .codex/ or
710
718
  // .sinapse-ai/), so neither loop above reaches it. Without it, a Codex user's
@@ -878,6 +886,7 @@ module.exports = {
878
886
  deliverClaudeNativeAdapters,
879
887
  getClaudeHookName,
880
888
  reconcileClaudeHookSettings,
889
+ copyReactBitsCorpusSync,
881
890
  FOLDERS_TO_COPY,
882
891
  ROOT_FILES_TO_COPY,
883
892
  CODEX_ROOT_FILES,
@@ -11,10 +11,12 @@ const {
11
11
  syncCodexNativeAgents,
12
12
  } = require('../.codex/scripts/sync-codex-native');
13
13
  const { resolveCodexAgent } = require('../.codex/scripts/resolve-codex-agent');
14
+ const { GLOBAL_SUPPLEMENTAL_PROVIDER_SKILL_IDS } = require('../bin/lib/provider-contract');
14
15
 
15
16
  const PROJECT_ROOT = path.resolve(__dirname, '..');
16
17
  const CLAUDE_AGENTS_DIR = '.claude/agents';
17
18
  const CLAUDE_SKILLS_DIR = '.claude/skills';
19
+ const SUPPLEMENTAL_PROVIDER_SKILL_IDS = GLOBAL_SUPPLEMENTAL_PROVIDER_SKILL_IDS;
18
20
 
19
21
  function writeFileAtomically(filePath, content) {
20
22
  const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
@@ -136,11 +138,13 @@ function collectClaudeSkills(projectRoot = PROJECT_ROOT) {
136
138
  catalog.genericAgentSkillId,
137
139
  ])].sort();
138
140
 
139
- return skillIds.map((skillId) => {
141
+ return [...new Set([...skillIds, ...SUPPLEMENTAL_PROVIDER_SKILL_IDS])].sort().map((skillId) => {
140
142
  const nativePath = path.join(projectRoot, '.agents', 'skills', skillId, 'SKILL.md');
141
143
  const metadata = parseSkillMetadata(fs.readFileSync(nativePath, 'utf8'));
142
144
  let content;
143
- if (skillId === catalog.genericAgentSkillId) {
145
+ if (SUPPLEMENTAL_PROVIDER_SKILL_IDS.includes(skillId)) {
146
+ content = fs.readFileSync(nativePath, 'utf8');
147
+ } else if (skillId === catalog.genericAgentSkillId) {
144
148
  content = renderClaudeGenericSkill(skillId, metadata.description);
145
149
  } else if (skillId === 'sinapse-loop' || skillId === 'sinapse-spec-driven') {
146
150
  content = renderClaudeWorkflowSkill(skillId, metadata.description);
@@ -195,6 +199,7 @@ if (require.main === module) main();
195
199
  module.exports = {
196
200
  CLAUDE_AGENTS_DIR,
197
201
  CLAUDE_SKILLS_DIR,
202
+ SUPPLEMENTAL_PROVIDER_SKILL_IDS,
198
203
  claudeAgentFileName,
199
204
  renderClaudeAgent,
200
205
  collectClaudeSkills,
@@ -8,6 +8,11 @@ const yaml = require('js-yaml');
8
8
  const legacyClaudeCommandHashes = require('../packages/installer/src/migrations/legacy-claude-agent-command-hashes.json');
9
9
 
10
10
  const { validateNativeCodex } = require('../.codex/scripts/validate-codex-native');
11
+ const {
12
+ REACT_BITS_CORPUS_RELATIVE_PATH,
13
+ REQUIRED_REACT_BITS_CORPUS_FILES,
14
+ hasCompleteReactBitsCorpus,
15
+ } = require('../packages/installer/src/installer/react-bits-corpus');
11
16
 
12
17
  const PROJECT_ROOT = path.resolve(__dirname, '..');
13
18
 
@@ -125,10 +130,13 @@ function validateClaudeNative(projectRoot = PROJECT_ROOT) {
125
130
  try {
126
131
  const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
127
132
  expectedSkills = (manifest.skillIds || []).map((skillId) => ({ skillId }));
128
- if (expectedSkills.length !== 36) errors.push(`Claude skill manifest must contain 36 IDs, found ${expectedSkills.length}`);
133
+ if (expectedSkills.length !== 37) errors.push(`Claude skill manifest must contain 37 IDs, found ${expectedSkills.length}`);
129
134
  for (const alias of ['sinapse', 'sinapse-orqx', 'snps', 'snps-orqx', 'sinapse-agent']) {
130
135
  if (!manifest.skillIds.includes(alias)) errors.push(`Claude skill manifest is missing public alias ${alias}`);
131
136
  }
137
+ if (!manifest.skillIds.includes('react-bits-frontend')) {
138
+ errors.push('Claude skill manifest is missing react-bits-frontend');
139
+ }
132
140
  } catch (error) {
133
141
  errors.push(`Claude skill manifest: ${error.message}`);
134
142
  }
@@ -209,6 +217,17 @@ function validateProviderAdapters(projectRoot = PROJECT_ROOT) {
209
217
  if (!pkg.files.includes(entry)) errors.push(`package.json files[] is missing ${entry}`);
210
218
  }
211
219
  if (pkg.files.includes('.codex/skills/')) errors.push('package.json must not publish the legacy .codex/skills root');
220
+ if (!hasCompleteReactBitsCorpus(projectRoot)) {
221
+ errors.push(`React Bits corpus is incomplete: expected ${REQUIRED_REACT_BITS_CORPUS_FILES.length} files under ${REACT_BITS_CORPUS_RELATIVE_PATH}`);
222
+ }
223
+ for (const relativePath of [
224
+ path.join('.agents', 'skills', 'react-bits-frontend', 'SKILL.md'),
225
+ path.join('.claude', 'skills', 'react-bits-frontend', 'SKILL.md'),
226
+ ]) {
227
+ if (!fs.existsSync(path.join(projectRoot, relativePath))) {
228
+ errors.push(`React Bits provider skill is missing: ${relativePath}`);
229
+ }
230
+ }
212
231
  return {
213
232
  ok: errors.length === 0,
214
233
  errors,
@@ -240,4 +259,5 @@ module.exports = {
240
259
  validateClaudeHookSettings,
241
260
  validateClaudeNative,
242
261
  validateProviderAdapters,
262
+ REQUIRED_REACT_BITS_CORPUS_FILES,
243
263
  };
@@ -28,6 +28,8 @@ Kinetic e o diretor da squad. Recebe os Animation Briefs do Decoder (animation-i
28
28
  - Garantir coesao entre animacoes de diferentes agentes
29
29
  - Orquestrar o workflow prompt-to-animation-cycle
30
30
  - Gerar relatorios de entrega
31
+ - Roteiar trabalho React Bits pelo task `orchestrate-react-bits-frontend` e pelo
32
+ corpus operacional antes de delegar implementacao
31
33
 
32
34
  ## Pipeline de Orquestracao
33
35
 
@@ -64,6 +66,7 @@ Kinetic → Review final → Entrega
64
66
  | Hover effects | css-motion-artist | shader-artist |
65
67
  | Loading animations | css-motion-artist | motion-choreographer |
66
68
  | Camera movements 3D | threejs-architect | motion-choreographer |
69
+ | React Bits frontend | orchestrate-react-bits-frontend | animation-performance-engineer |
67
70
 
68
71
  ## Criterios de Qualidade
69
72
 
@@ -86,6 +89,7 @@ Toda animacao deve passar por:
86
89
  | Definir timing e coreografia | motion-choreographer (Tempo) |
87
90
  | Implementar scroll animation | scroll-narrative-engineer (Parallax) |
88
91
  | Criar sistema de particulas | generative-particle-engineer (Cloud) |
92
+ | Orquestrar frontend React Bits | orchestrate-react-bits-frontend |
89
93
  | Otimizar performance | animation-performance-engineer (Benchmark) |
90
94
 
91
95
  ## NON-NEGOTIABLE: ORCHESTRATE, DON'T EXECUTE
@@ -135,6 +139,9 @@ integration:
135
139
  - agent: "animation-performance-engineer (Benchmark)"
136
140
  when: "Quality gate before delivery — perf audit, 60fps validation, mobile check"
137
141
  context_passed: "all built animations, target devices, perf budget"
142
+ - agent: "orchestrate-react-bits-frontend"
143
+ when: "Frontend request that needs React Bits discovery, selection, installation or implementation"
144
+ context_passed: "project stack, target surface, desired interaction, accessibility and performance constraints"
138
145
  receives_from:
139
146
  - agent: "@sinapse-orqx (Imperator)"
140
147
  when: "Animation/motion request routed from ecosystem"