sinapse-ai 7.0.4 → 7.1.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.
Files changed (36) hide show
  1. package/.sinapse-ai/core-config.yaml +2 -26
  2. package/.sinapse-ai/data/entity-registry.yaml +759 -926
  3. package/.sinapse-ai/data/registry-update-log.jsonl +22 -0
  4. package/.sinapse-ai/infrastructure/scripts/ide-sync/index.js +1 -49
  5. package/.sinapse-ai/infrastructure/scripts/validate-parity.js +0 -7
  6. package/.sinapse-ai/install-manifest.yaml +11 -43
  7. package/README.en.md +6 -11
  8. package/README.md +6 -11
  9. package/bin/modules/env-config.js +1 -2
  10. package/bin/sinapse-init.js +23 -188
  11. package/docs/ide-integration.md +22 -263
  12. package/docs/installation/README.md +4 -6
  13. package/docs/installation/faq.md +10 -33
  14. package/docs/installation/linux.md +0 -23
  15. package/docs/installation/macos.md +0 -10
  16. package/docs/installation/troubleshooting.md +5 -9
  17. package/docs/installation/v4-quick-start.md +1 -1
  18. package/docs/installation/windows.md +0 -18
  19. package/package.json +1 -9
  20. package/packages/installer/src/config/ide-configs.js +3 -49
  21. package/squads/squad-cloning/squad.yaml +2 -2
  22. package/squads/squad-finance/squad.yaml +2 -2
  23. package/squads/squad-product/agents/product-orqx.md +1 -1
  24. package/squads/squad-product/squad.yaml +1 -1
  25. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/antigravity.js +0 -105
  26. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/cursor.js +0 -94
  27. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/github-copilot.js +0 -184
  28. package/.sinapse-ai/infrastructure/scripts/validate-gemini-integration.js +0 -151
  29. package/.sinapse-ai/product/templates/ide-rules/antigravity-rules.md +0 -115
  30. package/.sinapse-ai/product/templates/ide-rules/copilot-rules.md +0 -92
  31. package/.sinapse-ai/product/templates/ide-rules/cursor-rules.md +0 -115
  32. package/.sinapse-ai/product/templates/ide-rules/gemini-rules.md +0 -87
  33. package/docs/pt/platforms/antigravity.md +0 -508
  34. package/docs/pt/platforms/cursor.md +0 -633
  35. package/docs/pt/platforms/gemini-cli.md +0 -481
  36. package/docs/pt/platforms/github-copilot.md +0 -478
@@ -25,13 +25,9 @@ const path = require('path');
25
25
  /**
26
26
  * IDE Configuration Metadata
27
27
  *
28
- * SINAPSE v4 supports 6 main IDEs:
28
+ * SINAPSE v4 supports 2 main IDEs:
29
29
  * - Claude Code (Anthropic's official CLI) - Recommended
30
30
  * - Codex CLI (OpenAI coding CLI)
31
- * - Gemini CLI (Google AI coding CLI)
32
- * - Cursor (AI-first code editor)
33
- * - GitHub Copilot (GitHub's AI pair programmer)
34
- * - AntiGravity (Google agentic platform)
35
31
  */
36
32
  const IDE_CONFIGS = {
37
33
  'claude-code': {
@@ -47,55 +43,13 @@ const IDE_CONFIGS = {
47
43
  codex: {
48
44
  name: 'Codex CLI',
49
45
  description: '',
50
- configFile: 'AGENTS.md',
46
+ configFile: '.codex/instructions.md',
51
47
  template: 'ide-rules/codex-rules.md',
52
- requiresDirectory: false,
48
+ requiresDirectory: true,
53
49
  format: 'text',
54
50
  recommended: true,
55
51
  agentFolder: path.join('.codex', 'agents'),
56
52
  },
57
- gemini: {
58
- name: 'Gemini CLI',
59
- description: '',
60
- configFile: path.join('.gemini', 'rules.md'),
61
- template: 'ide-rules/gemini-rules.md',
62
- requiresDirectory: true,
63
- format: 'text',
64
- agentFolder: path.join('.gemini', 'rules', 'SINAPSE', 'agents'),
65
- },
66
- cursor: {
67
- name: 'Cursor',
68
- description: '',
69
- configFile: path.join('.cursor', 'rules.md'),
70
- template: 'ide-rules/cursor-rules.md',
71
- requiresDirectory: true,
72
- format: 'text',
73
- agentFolder: path.join('.cursor', 'rules'),
74
- },
75
- 'github-copilot': {
76
- name: 'GitHub Copilot',
77
- description: '',
78
- configFile: path.join('.github', 'copilot-instructions.md'),
79
- template: 'ide-rules/copilot-rules.md',
80
- requiresDirectory: true,
81
- format: 'text',
82
- agentFolder: path.join('.github', 'agents'),
83
- },
84
- antigravity: {
85
- name: 'AntiGravity',
86
- description: '',
87
- configFile: path.join('.antigravity', 'rules.md'),
88
- template: 'ide-rules/antigravity-rules.md',
89
- requiresDirectory: true,
90
- format: 'text',
91
- agentFolder: path.join('.agent', 'workflows'),
92
- specialConfig: {
93
- type: 'antigravity',
94
- configJsonPath: path.join('.antigravity', 'antigravity.json'),
95
- workflowsFolder: path.join('.agent', 'workflows'),
96
- agentsFolder: path.join('.antigravity', 'agents'),
97
- },
98
- },
99
53
  };
100
54
 
101
55
  /**
@@ -264,7 +264,7 @@ config:
264
264
  tier2: 2
265
265
  tier3: 3
266
266
  tools:
267
- transcription: "OpenClaw (Whisper)"
267
+ transcription: "Whisper API"
268
268
  search: "Brave Search"
269
269
  processing: "Claude Code (Max)"
270
270
  sync: "Syncthing"
@@ -274,7 +274,7 @@ dependencies:
274
274
  python: []
275
275
  squads: []
276
276
  external:
277
- - name: OpenClaw
277
+ - name: Whisper
278
278
  purpose: "Whisper transcription, Playwright capture"
279
279
  required: false
280
280
  - name: Brave Search
@@ -189,12 +189,12 @@ cross_squad_connections:
189
189
  inbound:
190
190
  - squad-commercial: "Dados de vendas, propostas fechadas, pipeline comercial"
191
191
  - squad-growth: "Metricas de growth, CAC, conversion rates, funnel data"
192
- - squad-operations-hub: "Dados operacionais, timesheets, utilizacao de equipe"
192
+ - squad-commercial: "Dados operacionais, timesheets, utilizacao de equipe"
193
193
  - squad-product: "Custos de desenvolvimento, estimativas de esforco"
194
194
  outbound:
195
195
  - squad-commercial: "Rate cards, pricing tiers, margens por servico para propostas"
196
196
  - squad-growth: "Unit economics, LTV/CAC, ROI por canal para otimizacao"
197
- - squad-operations-hub: "Budget targets, cost centers, vendor budgets"
197
+ - squad-commercial: "Budget targets, cost centers, vendor budgets"
198
198
  - squad-brand: "Investimento em marca, ROI de branding"
199
199
 
200
200
  tags:
@@ -80,7 +80,7 @@ Product experiments are owned by squad-product. However:
80
80
 
81
81
  ## Cross-Squad Handoffs
82
82
  - **Receives from:** squad-commercial (NPS, feature requests), squad-research (market data)
83
- - **Sends to:** squad-operations-hub (resource needs), squad-design (discovery opportunities)
83
+ - **Sends to:** squad-commercial (resource needs), squad-design (discovery opportunities)
84
84
 
85
85
  ## Escalation
86
86
 
@@ -233,7 +233,7 @@ cross_squad_handoffs:
233
233
  receives: "UX designs, design system components, accessibility audit"
234
234
 
235
235
  outbound:
236
- - to: squad-operations-hub
236
+ - to: squad-commercial
237
237
  sends: "Resource needs, sprint capacity forecasts, process automation requests"
238
238
  - to: squad-design
239
239
  sends: "Discovery opportunities for UX, product specs for UI design"
@@ -1,105 +0,0 @@
1
- /**
2
- * Antigravity Transformer - Cursor-style format
3
- * @story 6.19 - IDE Command Auto-Sync System
4
- *
5
- * Format: Similar to Cursor, condensed rules format
6
- * Target: .antigravity/rules/agents/*.md
7
- */
8
-
9
- const { getVisibleCommands, normalizeCommands } = require('../agent-parser');
10
-
11
- /**
12
- * Transform agent data to Antigravity format
13
- * @param {object} agentData - Parsed agent data from agent-parser
14
- * @returns {string} - Transformed content
15
- */
16
- function transform(agentData) {
17
- const agent = agentData.agent || {};
18
- const persona = agentData.persona_profile || {};
19
-
20
- const icon = agent.icon || '🤖';
21
- const name = agent.name || agentData.id;
22
- const title = agent.title || 'SINAPSE Agent';
23
- const whenToUse = agent.whenToUse || 'Use this agent for specific tasks';
24
- const archetype = persona.archetype || '';
25
-
26
- // Get quick visibility commands (normalized to consistent format)
27
- const allCommands = normalizeCommands(agentData.commands || []);
28
- const quickCommands = getVisibleCommands(allCommands, 'quick');
29
- const keyCommands = getVisibleCommands(allCommands, 'key');
30
-
31
- // Build content (similar to Cursor)
32
- let content = `# ${name} (@${agentData.id})
33
-
34
- ${icon} **${title}**${archetype ? ` | ${archetype}` : ''}
35
-
36
- > ${whenToUse}
37
-
38
- `;
39
-
40
- // Add quick commands section
41
- if (quickCommands.length > 0) {
42
- content += `## Quick Commands
43
-
44
- `;
45
- for (const cmd of quickCommands) {
46
- content += `- \`*${cmd.name}\` - ${cmd.description || 'No description'}\n`;
47
- }
48
- content += '\n';
49
- }
50
-
51
- // Add key commands if different from quick
52
- const keyOnlyCommands = keyCommands.filter(
53
- k => !quickCommands.some(q => q.name === k.name)
54
- );
55
- if (keyOnlyCommands.length > 0) {
56
- content += `## Key Commands
57
-
58
- `;
59
- for (const cmd of keyOnlyCommands) {
60
- content += `- \`*${cmd.name}\` - ${cmd.description || 'No description'}\n`;
61
- }
62
- content += '\n';
63
- }
64
-
65
- // Add all commands for reference (allCommands already normalized above)
66
- if (allCommands.length > quickCommands.length + keyOnlyCommands.length) {
67
- content += `## All Commands
68
-
69
- `;
70
- for (const cmd of allCommands) {
71
- content += `- \`*${cmd.name}\` - ${cmd.description || 'No description'}\n`;
72
- }
73
- content += '\n';
74
- }
75
-
76
- // Add collaboration section if available
77
- if (agentData.sections.collaboration) {
78
- content += `## Collaboration
79
-
80
- ${agentData.sections.collaboration}
81
-
82
- `;
83
- }
84
-
85
- content += `---
86
- *SINAPSE Agent - Synced from .sinapse-ai/development/agents/${agentData.filename}*
87
- `;
88
-
89
- return content;
90
- }
91
-
92
- /**
93
- * Get the target filename for this agent
94
- * @param {object} agentData - Parsed agent data
95
- * @returns {string} - Target filename
96
- */
97
- function getFilename(agentData) {
98
- return agentData.filename;
99
- }
100
-
101
- module.exports = {
102
- transform,
103
- getFilename,
104
- format: 'cursor-style',
105
- };
@@ -1,94 +0,0 @@
1
- /**
2
- * Cursor Transformer - Condensed rules format
3
- * @story 6.19 - IDE Command Auto-Sync System
4
- *
5
- * Format: Condensed markdown with icon, title, quick commands
6
- * Target: .cursor/rules/agents/*.md
7
- */
8
-
9
- const { getVisibleCommands, normalizeCommands } = require('../agent-parser');
10
-
11
- /**
12
- * Transform agent data to Cursor format
13
- * @param {object} agentData - Parsed agent data from agent-parser
14
- * @returns {string} - Transformed content
15
- */
16
- function transform(agentData) {
17
- const agent = agentData.agent || {};
18
- const persona = agentData.persona_profile || {};
19
-
20
- const icon = agent.icon || '🤖';
21
- const name = agent.name || agentData.id;
22
- const title = agent.title || 'SINAPSE Agent';
23
- const whenToUse = agent.whenToUse || 'Use this agent for specific tasks';
24
- const archetype = persona.archetype || '';
25
-
26
- // Get quick visibility commands (normalized to consistent format)
27
- const allCommands = normalizeCommands(agentData.commands || []);
28
- const quickCommands = getVisibleCommands(allCommands, 'quick');
29
- const keyCommands = getVisibleCommands(allCommands, 'key');
30
-
31
- // Build content
32
- let content = `# ${name} (@${agentData.id})
33
-
34
- ${icon} **${title}**${archetype ? ` | ${archetype}` : ''}
35
-
36
- > ${whenToUse}
37
-
38
- `;
39
-
40
- // Add quick commands section
41
- if (quickCommands.length > 0) {
42
- content += `## Quick Commands
43
-
44
- `;
45
- for (const cmd of quickCommands) {
46
- content += `- \`*${cmd.name}\` - ${cmd.description || 'No description'}\n`;
47
- }
48
- content += '\n';
49
- }
50
-
51
- // Add key commands if different from quick
52
- const keyOnlyCommands = keyCommands.filter(
53
- k => !quickCommands.some(q => q.name === k.name)
54
- );
55
- if (keyOnlyCommands.length > 0) {
56
- content += `## Key Commands
57
-
58
- `;
59
- for (const cmd of keyOnlyCommands) {
60
- content += `- \`*${cmd.name}\` - ${cmd.description || 'No description'}\n`;
61
- }
62
- content += '\n';
63
- }
64
-
65
- // Add collaboration section if available
66
- if (agentData.sections.collaboration) {
67
- content += `## Collaboration
68
-
69
- ${agentData.sections.collaboration}
70
-
71
- `;
72
- }
73
-
74
- content += `---
75
- *SINAPSE Agent - Synced from .sinapse-ai/development/agents/${agentData.filename}*
76
- `;
77
-
78
- return content;
79
- }
80
-
81
- /**
82
- * Get the target filename for this agent
83
- * @param {object} agentData - Parsed agent data
84
- * @returns {string} - Target filename
85
- */
86
- function getFilename(agentData) {
87
- return agentData.filename;
88
- }
89
-
90
- module.exports = {
91
- transform,
92
- getFilename,
93
- format: 'condensed-rules',
94
- };
@@ -1,184 +0,0 @@
1
- /**
2
- * GitHub Copilot Transformer - YAML frontmatter + condensed markdown
3
- * @story 6.19 - IDE Command Auto-Sync System
4
- * @issue #138 - Agent files not compatible with GitHub Copilot
5
- *
6
- * Format: .agent.md files with YAML frontmatter (--- delimiters)
7
- * Target: .github/agents/*.agent.md
8
- *
9
- * GitHub Copilot custom agents require:
10
- * - YAML frontmatter with `description` (required), `name`, `tools`
11
- * - Markdown body under 30,000 characters
12
- * - File extension: .agent.md
13
- *
14
- * @see https://docs.github.com/en/copilot/reference/custom-agents-configuration
15
- */
16
-
17
- const { normalizeCommands, getVisibleCommands } = require('../agent-parser');
18
-
19
- /**
20
- * Transform agent data to GitHub Copilot custom agent format
21
- * @param {object} agentData - Parsed agent data from agent-parser
22
- * @returns {string} - Transformed content with YAML frontmatter
23
- */
24
- function transform(agentData) {
25
- const agent = agentData.agent || {};
26
- const persona = agentData.persona_profile || {};
27
- const yamlData = agentData.yaml || {};
28
- const personaBlock = yamlData.persona || {};
29
-
30
- const id = agent.id || agentData.id;
31
- const name = agent.name || id;
32
- const title = agent.title || 'SINAPSE Agent';
33
- const icon = agent.icon || '';
34
- const description = escapeYamlString(agent.whenToUse || `${title} agent for development tasks`);
35
-
36
- // Build YAML frontmatter
37
- const frontmatter = [
38
- '---',
39
- `name: ${id}`,
40
- `description: '${description}'`,
41
- `tools: ['read', 'edit', 'search', 'execute']`,
42
- '---',
43
- ].join('\n');
44
-
45
- // Build markdown body
46
- const body = buildMarkdownBody({
47
- id,
48
- name,
49
- title,
50
- icon,
51
- personaBlock,
52
- persona,
53
- commands: agentData.commands || [],
54
- sections: agentData.sections || {},
55
- corePrinciples: yamlData.core_principles,
56
- filename: agentData.filename,
57
- });
58
-
59
- const content = `${frontmatter}\n\n${body}`;
60
-
61
- // Enforce 30K character limit
62
- if (content.length > 30000) {
63
- return truncateContent(content, 30000);
64
- }
65
-
66
- return content;
67
- }
68
-
69
- /**
70
- * Build the markdown body for the Copilot agent prompt
71
- * @param {object} params - Agent parameters
72
- * @returns {string} - Markdown body
73
- */
74
- function buildMarkdownBody(params) {
75
- const { id, name, title, icon, personaBlock, persona, commands, sections, filename } = params;
76
-
77
- const parts = [];
78
-
79
- // Header
80
- const headerIcon = icon ? `${icon} ` : '';
81
- parts.push(`# ${headerIcon}${name} Agent (@${id})\n`);
82
-
83
- // Role description
84
- if (personaBlock.role) {
85
- parts.push(`You are an expert ${personaBlock.role}.\n`);
86
- } else {
87
- parts.push(`You are an expert ${title}.\n`);
88
- }
89
-
90
- // Style
91
- if (personaBlock.style) {
92
- parts.push(`## Style\n\n${personaBlock.style}\n`);
93
- }
94
-
95
- // Core principles (may be in persona block or at root level of YAML)
96
- const corePrinciples = personaBlock.core_principles || params.corePrinciples;
97
- if (corePrinciples && Array.isArray(corePrinciples)) {
98
- parts.push('## Core Principles\n');
99
- for (const principle of corePrinciples) {
100
- // Handle both string and object formats (YAML may parse "KEY: value" as {KEY: value})
101
- if (typeof principle === 'string') {
102
- parts.push(`- ${principle}`);
103
- } else if (typeof principle === 'object' && principle !== null) {
104
- const entries = Object.entries(principle);
105
- for (const [key, value] of entries) {
106
- parts.push(`- ${key}: ${value}`);
107
- }
108
- }
109
- }
110
- parts.push('');
111
- }
112
-
113
- // Commands reference
114
- const allCommands = normalizeCommands(commands);
115
- const keyCommands = getVisibleCommands(allCommands, 'key');
116
- const quickCommands = getVisibleCommands(allCommands, 'quick');
117
- const displayCommands = keyCommands.length > 0 ? keyCommands : quickCommands.slice(0, 10);
118
-
119
- if (displayCommands.length > 0) {
120
- parts.push('## Commands\n');
121
- parts.push('Use `*` prefix for commands:\n');
122
- for (const cmd of displayCommands) {
123
- parts.push(`- \`*${cmd.name}\` - ${cmd.description || 'No description'}`);
124
- }
125
- parts.push('');
126
- }
127
-
128
- // Collaboration section (condensed)
129
- if (sections.collaboration) {
130
- parts.push(`## Collaboration\n\n${sections.collaboration}\n`);
131
- }
132
-
133
- // Sync footer
134
- parts.push('---');
135
- parts.push(`*SINAPSE Agent - Synced from .sinapse-ai/development/agents/${filename}*`);
136
- parts.push('');
137
-
138
- return parts.join('\n');
139
- }
140
-
141
- /**
142
- * Escape a string for use as a YAML single-quoted value
143
- * Single quotes inside the string must be doubled
144
- * @param {string} str - Input string
145
- * @returns {string} - Escaped string
146
- */
147
- function escapeYamlString(str) {
148
- if (!str) return '';
149
- // In YAML single-quoted strings, single quotes are escaped by doubling them
150
- return str.replace(/'/g, "''");
151
- }
152
-
153
- /**
154
- * Truncate content to fit within character limit while keeping structure valid
155
- * @param {string} content - Full content
156
- * @param {number} maxChars - Maximum characters
157
- * @returns {string} - Truncated content
158
- */
159
- function truncateContent(content, maxChars) {
160
- // Find the last complete section before the limit
161
- const truncated = content.substring(0, maxChars - 100);
162
- const lastNewline = truncated.lastIndexOf('\n\n');
163
-
164
- if (lastNewline > 0) {
165
- return truncated.substring(0, lastNewline) + '\n\n---\n*Content truncated to fit 30K limit*\n';
166
- }
167
- return truncated + '\n\n---\n*Content truncated to fit 30K limit*\n';
168
- }
169
-
170
- /**
171
- * Get the target filename for this agent (with .agent.md extension)
172
- * @param {object} agentData - Parsed agent data
173
- * @returns {string} - Target filename (e.g., "dev.agent.md")
174
- */
175
- function getFilename(agentData) {
176
- const id = (agentData.agent && agentData.agent.id) || agentData.id;
177
- return `${id}.agent.md`;
178
- }
179
-
180
- module.exports = {
181
- transform,
182
- getFilename,
183
- format: 'github-copilot',
184
- };
@@ -1,151 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const fs = require('fs');
5
- const path = require('path');
6
-
7
- function getDefaultOptions() {
8
- const projectRoot = process.cwd();
9
- return {
10
- projectRoot,
11
- rulesFile: path.join(projectRoot, '.gemini', 'rules.md'),
12
- agentsDir: path.join(projectRoot, '.gemini', 'rules', 'SINAPSE', 'agents'),
13
- commandsDir: path.join(projectRoot, '.gemini', 'commands'),
14
- extensionDir: path.join(projectRoot, 'packages', 'gemini-sinapse-extension'),
15
- sourceAgentsDir: path.join(projectRoot, '.sinapse-ai', 'development', 'agents'),
16
- quiet: false,
17
- json: false,
18
- };
19
- }
20
-
21
- function parseArgs(argv = process.argv.slice(2)) {
22
- const args = new Set(argv);
23
- return {
24
- quiet: args.has('--quiet') || args.has('-q'),
25
- json: args.has('--json'),
26
- };
27
- }
28
-
29
- function countMarkdownFiles(dirPath) {
30
- if (!fs.existsSync(dirPath)) return 0;
31
- return fs.readdirSync(dirPath).filter((f) => f.endsWith('.md')).length;
32
- }
33
-
34
- function validateGeminiIntegration(options = {}) {
35
- const projectRoot = options.projectRoot || process.cwd();
36
- const resolved = {
37
- ...getDefaultOptions(),
38
- ...options,
39
- projectRoot,
40
- rulesFile: options.rulesFile || path.join(projectRoot, '.gemini', 'rules.md'),
41
- agentsDir: options.agentsDir || path.join(projectRoot, '.gemini', 'rules', 'SINAPSE', 'agents'),
42
- commandsDir: options.commandsDir || path.join(projectRoot, '.gemini', 'commands'),
43
- extensionDir: options.extensionDir || path.join(projectRoot, 'packages', 'gemini-sinapse-extension'),
44
- sourceAgentsDir: options.sourceAgentsDir || path.join(projectRoot, '.sinapse-ai', 'development', 'agents'),
45
- };
46
- const errors = [];
47
- const warnings = [];
48
-
49
- if (!fs.existsSync(resolved.rulesFile)) {
50
- warnings.push(`Gemini rules file not found yet: ${path.relative(resolved.projectRoot, resolved.rulesFile)}`);
51
- }
52
-
53
- if (!fs.existsSync(resolved.agentsDir)) {
54
- errors.push(`Missing Gemini agents dir: ${path.relative(resolved.projectRoot, resolved.agentsDir)}`);
55
- }
56
- if (!fs.existsSync(resolved.commandsDir)) {
57
- errors.push(`Missing Gemini commands dir: ${path.relative(resolved.projectRoot, resolved.commandsDir)}`);
58
- }
59
-
60
- const sourceCount = countMarkdownFiles(resolved.sourceAgentsDir);
61
- const geminiCount = countMarkdownFiles(resolved.agentsDir);
62
- const commandFiles = fs.existsSync(resolved.commandsDir)
63
- ? fs.readdirSync(resolved.commandsDir).filter((f) => f.endsWith('.toml'))
64
- : [];
65
- const expectedCommandCount = sourceCount > 0 ? sourceCount + 1 : 0;
66
-
67
- if (sourceCount > 0 && commandFiles.length !== expectedCommandCount) {
68
- warnings.push(`Gemini command count differs from source (${commandFiles.length}/${expectedCommandCount})`);
69
- }
70
- if (!commandFiles.includes('sinapse-menu.toml')) {
71
- errors.push(`Missing Gemini command file: ${path.relative(resolved.projectRoot, path.join(resolved.commandsDir, 'sinapse-menu.toml'))}`);
72
- }
73
- if (sourceCount > 0 && geminiCount !== sourceCount) {
74
- warnings.push(`Gemini agent count differs from source (${geminiCount}/${sourceCount})`);
75
- }
76
-
77
- const requiredExtensionFiles = [
78
- 'extension.json',
79
- 'README.md',
80
- path.join('commands', 'sinapse-status.js'),
81
- path.join('commands', 'sinapse-agents.js'),
82
- path.join('commands', 'sinapse-validate.js'),
83
- path.join('hooks', 'hooks.json'),
84
- ];
85
-
86
- for (const rel of requiredExtensionFiles) {
87
- const abs = path.join(resolved.extensionDir, rel);
88
- if (!fs.existsSync(abs)) {
89
- errors.push(`Missing Gemini extension file: ${path.relative(resolved.projectRoot, abs)}`);
90
- }
91
- }
92
-
93
- return {
94
- ok: errors.length === 0,
95
- errors,
96
- warnings,
97
- metrics: {
98
- sourceAgents: sourceCount,
99
- geminiAgents: geminiCount,
100
- geminiCommands: commandFiles.length,
101
- },
102
- };
103
- }
104
-
105
- function formatHumanReport(result) {
106
- if (result.ok) {
107
- const lines = [
108
- `✅ Gemini integration validation passed (agents: ${result.metrics.geminiAgents}, commands: ${result.metrics.geminiCommands})`,
109
- ];
110
- if (result.warnings.length > 0) {
111
- lines.push(...result.warnings.map((w) => `⚠️ ${w}`));
112
- }
113
- return lines.join('\n');
114
- }
115
- const lines = [
116
- `❌ Gemini integration validation failed (${result.errors.length} issue(s))`,
117
- ...result.errors.map((e) => `- ${e}`),
118
- ];
119
- if (result.warnings.length > 0) {
120
- lines.push(...result.warnings.map((w) => `⚠️ ${w}`));
121
- }
122
- return lines.join('\n');
123
- }
124
-
125
- function main() {
126
- const args = parseArgs();
127
- const result = validateGeminiIntegration(args);
128
-
129
- if (!args.quiet) {
130
- if (args.json) {
131
- console.log(JSON.stringify(result, null, 2));
132
- } else {
133
- console.log(formatHumanReport(result));
134
- }
135
- }
136
-
137
- if (!result.ok) {
138
- process.exitCode = 1;
139
- }
140
- }
141
-
142
- if (require.main === module) {
143
- main();
144
- }
145
-
146
- module.exports = {
147
- validateGeminiIntegration,
148
- parseArgs,
149
- getDefaultOptions,
150
- countMarkdownFiles,
151
- };