sinapse-ai 1.6.1 → 1.8.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 (131) hide show
  1. package/.claude/CLAUDE.md +5 -11
  2. package/.claude/hooks/README.md +14 -1
  3. package/.claude/hooks/code-intel-pretool.cjs +115 -0
  4. package/.claude/hooks/enforce-delegation.cjs +31 -3
  5. package/.claude/hooks/enforce-framework-boundary.cjs +324 -0
  6. package/.claude/hooks/enforce-permission-mode.cjs +249 -0
  7. package/.claude/hooks/secret-scanning.cjs +34 -43
  8. package/.claude/hooks/synapse-engine.cjs +23 -23
  9. package/.claude/hooks/telemetry-post-tool.cjs +128 -0
  10. package/.claude/hooks/telemetry-stop.cjs +132 -0
  11. package/.claude/hooks/verify-packages.cjs +9 -2
  12. package/.claude/rules/documentation-first.md +1 -1
  13. package/.claude/rules/hook-governance.md +2 -0
  14. package/.sinapse-ai/cli/commands/health/index.js +24 -0
  15. package/.sinapse-ai/core/README.md +11 -0
  16. package/.sinapse-ai/core/config/config-loader.js +19 -0
  17. package/.sinapse-ai/core/config/merge-utils.js +8 -0
  18. package/.sinapse-ai/core/errors/constants.js +147 -0
  19. package/.sinapse-ai/core/errors/error-registry.js +176 -0
  20. package/.sinapse-ai/core/errors/index.js +50 -0
  21. package/.sinapse-ai/core/errors/serializer.js +147 -0
  22. package/.sinapse-ai/core/errors/sinapse-error.js +144 -0
  23. package/.sinapse-ai/core/errors/utils.js +187 -0
  24. package/.sinapse-ai/core/execution/build-orchestrator.js +47 -49
  25. package/.sinapse-ai/core/execution/build-state-manager.js +183 -31
  26. package/.sinapse-ai/core/execution/parallel-executor.js +7 -1
  27. package/.sinapse-ai/core/execution/semantic-merge-engine.js +26 -14
  28. package/.sinapse-ai/core/execution/subagent-dispatcher.js +201 -60
  29. package/.sinapse-ai/core/execution/wave-executor.js +4 -1
  30. package/.sinapse-ai/core/grounding/README.md +71 -11
  31. package/.sinapse-ai/core/health-check/checks/project/framework-config.js +38 -2
  32. package/.sinapse-ai/core/health-check/checks/project/package-json.js +47 -3
  33. package/.sinapse-ai/core/health-check/checks/services/gemini-cli.js +117 -0
  34. package/.sinapse-ai/core/health-check/checks/services/index.js +2 -0
  35. package/.sinapse-ai/core/health-check/healers/index.js +40 -3
  36. package/.sinapse-ai/core/ideation/ideation-engine.js +212 -107
  37. package/.sinapse-ai/core/ids/gate-evaluator.js +318 -0
  38. package/.sinapse-ai/core/ids/gates/g5-semantic-handshake.js +190 -0
  39. package/.sinapse-ai/core/ids/gates/g6-ci-integrity.js +162 -0
  40. package/.sinapse-ai/core/ids/index.js +30 -0
  41. package/.sinapse-ai/core/memory/__tests__/active-modules.verify.js +11 -0
  42. package/.sinapse-ai/core/memory/gotchas-memory.js +37 -2
  43. package/.sinapse-ai/core/orchestration/agent-invoker.js +29 -6
  44. package/.sinapse-ai/core/orchestration/brownfield-handler.js +36 -3
  45. package/.sinapse-ai/core/orchestration/condition-evaluator.js +57 -0
  46. package/.sinapse-ai/core/orchestration/executors/epic-3-executor.js +76 -5
  47. package/.sinapse-ai/core/orchestration/executors/epic-4-executor.js +63 -17
  48. package/.sinapse-ai/core/orchestration/executors/epic-6-executor.js +153 -41
  49. package/.sinapse-ai/core/orchestration/executors/epic-executor.js +40 -0
  50. package/.sinapse-ai/core/orchestration/greenfield-handler.js +87 -3
  51. package/.sinapse-ai/core/orchestration/master-orchestrator.js +150 -10
  52. package/.sinapse-ai/core/orchestration/parallel-executor.js +6 -1
  53. package/.sinapse-ai/core/orchestration/recovery-handler.js +81 -8
  54. package/.sinapse-ai/core/orchestration/workflow-executor.js +41 -0
  55. package/.sinapse-ai/core/registry/registry-loader.js +71 -5
  56. package/.sinapse-ai/core/registry/squad-agent-resolver.js +253 -0
  57. package/.sinapse-ai/core/synapse/context/context-tracker.js +104 -9
  58. package/.sinapse-ai/core/synapse/context/index.js +19 -0
  59. package/.sinapse-ai/core/synapse/context/semantic-handshake-engine.js +555 -0
  60. package/.sinapse-ai/core/synapse/diagnostics/collectors/pipeline-collector.js +4 -2
  61. package/.sinapse-ai/core/synapse/engine.js +43 -3
  62. package/.sinapse-ai/core/telemetry/ids-sink.js +188 -0
  63. package/.sinapse-ai/core/utils/output-formatter.js +8 -290
  64. package/.sinapse-ai/core/utils/spawn-safe.js +186 -0
  65. package/.sinapse-ai/core-config.yaml +68 -1
  66. package/.sinapse-ai/data/entity-registry.yaml +15082 -13618
  67. package/.sinapse-ai/data/registry-update-log.jsonl +143 -0
  68. package/.sinapse-ai/development/agents/developer.md +2 -0
  69. package/.sinapse-ai/development/agents/devops.md +9 -0
  70. package/.sinapse-ai/development/external-executors/README.md +18 -0
  71. package/.sinapse-ai/development/external-executors/codex.md +56 -0
  72. package/.sinapse-ai/development/scripts/populate-entity-registry.js +65 -9
  73. package/.sinapse-ai/development/scripts/squad/squad-downloader.js +169 -14
  74. package/.sinapse-ai/development/tasks/delegate-to-external-executor.md +152 -0
  75. package/.sinapse-ai/development/tasks/github-devops-pre-push-quality-gate.md +46 -29
  76. package/.sinapse-ai/development/tasks/update-sinapse.md +3 -3
  77. package/.sinapse-ai/hooks/sinapse-brand-grounding.cjs +4 -7
  78. package/.sinapse-ai/hooks/sinapse-ds-grounding.cjs +5 -8
  79. package/.sinapse-ai/hooks/sinapse-vault-grounding.cjs +6 -9
  80. package/.sinapse-ai/infrastructure/integrations/ai-providers/ai-provider-factory.js +4 -1
  81. package/.sinapse-ai/infrastructure/integrations/ai-providers/claude-provider.js +57 -55
  82. package/.sinapse-ai/infrastructure/integrations/pm-adapters/github-adapter.js +9 -7
  83. package/.sinapse-ai/infrastructure/scripts/ide-sync/gemini-commands.js +298 -0
  84. package/.sinapse-ai/infrastructure/scripts/ide-sync/index.js +127 -6
  85. package/.sinapse-ai/infrastructure/scripts/ide-sync/persona-renderer.js +97 -0
  86. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/antigravity.js +121 -0
  87. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/cursor.js +119 -0
  88. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/github-copilot.js +191 -0
  89. package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/kimi.js +448 -0
  90. package/.sinapse-ai/install-manifest.yaml +218 -114
  91. package/.sinapse-ai/product/templates/engine/renderer.js +20 -1
  92. package/.sinapse-ai/scripts/pm.sh +18 -6
  93. package/bin/cli.js +17 -0
  94. package/bin/commands/agents.js +96 -0
  95. package/bin/commands/doctor.js +15 -0
  96. package/bin/commands/ideate.js +129 -0
  97. package/bin/commands/uninstall.js +40 -0
  98. package/bin/postinstall.js +50 -4
  99. package/bin/sinapse.js +146 -2
  100. package/bin/utils/secret-scanner-core.js +253 -0
  101. package/bin/utils/staged-secret-scan.js +106 -40
  102. package/docs/framework/collaboration-autonomy-plan.md +18 -18
  103. package/docs/guides/parallel-workflow.md +6 -6
  104. package/package.json +22 -5
  105. package/packages/installer/src/installer/git-hooks-installer.js +384 -0
  106. package/packages/installer/src/installer/sinapse-ai-installer.js +16 -0
  107. package/packages/installer/src/wizard/ide-config-generator.js +23 -0
  108. package/packages/installer/src/wizard/validators.js +38 -1
  109. package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +5 -1
  110. package/packages/installer/tests/unit/doctor/doctor-checks.test.js +44 -22
  111. package/packages/installer/tests/unit/git-hooks-installer.test.js +262 -0
  112. package/scripts/eval-runner.js +422 -0
  113. package/scripts/generate-install-manifest.js +13 -9
  114. package/scripts/generate-synapse-runtime.js +51 -0
  115. package/scripts/regenerate-orqx-stubs.ps1 +6 -5
  116. package/scripts/validate-all.js +1 -0
  117. package/scripts/validate-evals.js +466 -0
  118. package/scripts/validate-schemas.js +539 -0
  119. package/scripts/validate-squad-orqx.js +9 -2
  120. package/squads/claude-code-mastery/knowledge-base/memory-systems-reference.md +1 -1
  121. package/squads/squad-brand/templates/client-delivery-template.md +1 -1
  122. package/squads/squad-content/knowledge-base/social-compression-framework.md +1 -1
  123. package/squads/squad-council/knowledge-base/brand-strategy-models.md +1 -1
  124. package/.sinapse-ai/development/scripts/elicitation-engine.js +0 -385
  125. package/.sinapse-ai/development/scripts/elicitation-session-manager.js +0 -300
  126. package/.sinapse-ai/development/tasks/test-validation-task.md +0 -172
  127. package/docs/chrome-brain-upgrade-plan.md +0 -624
  128. package/docs/constitution-compliance.md +0 -87
  129. package/docs/mega-upgrade-orchestration-plan.md +0 -71
  130. package/docs/research-synthesis-for-upgrade.md +0 -511
  131. package/docs/security-audit-report.md +0 -306
@@ -7,8 +7,11 @@
7
7
  * @see Epic GEMINI-INT - Story 2: AI Provider Factory Pattern
8
8
  */
9
9
 
10
- const { spawn, execSync } = require('child_process');
10
+ const { execSync } = require('child_process');
11
11
  const { AIProvider } = require('./ai-provider');
12
+ // runSafe (cross-spawn): resolves claude.cmd on Windows and delivers the prompt
13
+ // via stdin/argv — same hardened spawn path used by the SubagentDispatcher.
14
+ const { runSafe } = require('../../../core/utils/spawn-safe');
12
15
 
13
16
  /**
14
17
  * Claude Code provider implementation
@@ -20,7 +23,7 @@ class ClaudeProvider extends AIProvider {
20
23
  /**
21
24
  * Create a Claude provider
22
25
  * @param {Object} [config={}] - Provider configuration
23
- * @param {string} [config.model='claude-3-5-sonnet'] - Model to use
26
+ * @param {string} [config.model] - Model override; omitted → CLI default model
24
27
  * @param {number} [config.timeout=300000] - Execution timeout
25
28
  * @param {boolean} [config.dangerouslySkipPermissions=false] - Skip permission prompts
26
29
  */
@@ -31,7 +34,9 @@ class ClaudeProvider extends AIProvider {
31
34
  timeout: config.timeout || 300000,
32
35
  maxRetries: config.maxRetries || 3,
33
36
  options: {
34
- model: config.model || 'claude-3-5-sonnet',
37
+ // No hardcoded model: stale IDs break the CLI. Only pass --model when
38
+ // a caller explicitly configures one; otherwise use the CLI's default.
39
+ model: config.model || null,
35
40
  dangerouslySkipPermissions: config.dangerouslySkipPermissions || false,
36
41
  ...config,
37
42
  },
@@ -82,59 +87,56 @@ class ClaudeProvider extends AIProvider {
82
87
  args.push('--model', options.model || this.options.model);
83
88
  }
84
89
 
85
- return new Promise((resolve, reject) => {
86
- let stdout = '';
87
- let stderr = '';
88
-
89
- // Spawn claude directly without shell interpolation (safer)
90
- const child = spawn(this.command, args, {
91
- cwd: workingDir,
92
- env: { ...process.env, ...options.env },
93
- windowsHide: true,
94
- stdio: ['pipe', 'pipe', 'pipe'],
95
- });
96
-
97
- // Write prompt via stdin to avoid shell injection
98
- child.stdin.write(prompt);
99
- child.stdin.end();
100
-
101
- const timeoutId = setTimeout(() => {
102
- child.kill('SIGTERM');
103
- reject(new Error(`Claude execution timed out after ${timeout}ms`));
104
- }, timeout);
105
-
106
- child.stdout.on('data', (data) => {
107
- stdout += data.toString();
108
- });
109
-
110
- child.stderr.on('data', (data) => {
111
- stderr += data.toString();
112
- });
113
-
114
- child.on('close', (code) => {
115
- clearTimeout(timeoutId);
116
- const duration = Date.now() - startTime;
117
-
118
- if (code === 0) {
119
- resolve({
120
- success: true,
121
- output: stdout.trim(),
122
- metadata: {
123
- duration,
124
- provider: 'claude',
125
- model: options.model || this.options.model,
126
- },
127
- });
128
- } else {
129
- reject(new Error(`Claude exited with code ${code}: ${stderr || stdout}`));
130
- }
131
- });
132
-
133
- child.on('error', (error) => {
134
- clearTimeout(timeoutId);
135
- reject(new Error(`Claude spawn error: ${error.message}`));
136
- });
90
+ // runSafe: argv-based spawn via cross-spawn (resolves claude.cmd on Windows),
91
+ // prompt delivered through stdin — shell injection structurally impossible.
92
+ const result = await runSafe(this.command, args, {
93
+ cwd: workingDir,
94
+ env: { ...process.env, ...options.env },
95
+ timeout,
96
+ input: prompt,
137
97
  });
98
+
99
+ const duration = Date.now() - startTime;
100
+
101
+ const stdout = (result.stdout || '').trim();
102
+ const stderr = (result.stderr || '').trim();
103
+
104
+ if (result.success) {
105
+ return {
106
+ success: true,
107
+ output: stdout,
108
+ metadata: {
109
+ duration,
110
+ provider: 'claude',
111
+ model: options.model || this.options.model || 'cli-default',
112
+ },
113
+ };
114
+ }
115
+
116
+ if (result.signal) {
117
+ throw new Error(
118
+ `Claude killed by signal ${result.signal} (timeout ${timeout}ms?): ${stderr || stdout}`,
119
+ );
120
+ }
121
+
122
+ // User-environment hooks (SessionEnd etc.) can fail AFTER the model already
123
+ // printed its full response, poisoning the exit code. In --print mode a
124
+ // non-empty stdout + hook-related stderr means the work was done — accept it
125
+ // (with a warning) instead of discarding paid output and retrying.
126
+ if (stdout.length > 0 && /hook/i.test(stderr)) {
127
+ return {
128
+ success: true,
129
+ output: stdout,
130
+ metadata: {
131
+ duration,
132
+ provider: 'claude',
133
+ model: options.model || this.options.model || 'cli-default',
134
+ warning: `non-zero exit (${result.code}) caused by environment hook failure: ${stderr.slice(0, 200)}`,
135
+ },
136
+ };
137
+ }
138
+
139
+ throw new Error(`Claude exited with code ${result.code}: ${stderr || stdout}`);
138
140
  }
139
141
 
140
142
  /**
@@ -7,7 +7,7 @@
7
7
  * @see Story 3.20 - PM Tool-Agnostic Integration (TR-3.20.3)
8
8
  */
9
9
 
10
- const { execSync } = require('child_process');
10
+ const { execFileSync } = require('child_process');
11
11
  const fs = require('fs');
12
12
  const yaml = require('js-yaml');
13
13
  const { PMAdapter } = require('../../scripts/pm-adapter');
@@ -293,8 +293,10 @@ class GitHubProjectsAdapter extends PMAdapter {
293
293
  * @returns {string} Command output
294
294
  */
295
295
  _execGH(args) {
296
- const command = `gh ${args.join(' ')}`;
297
- return execSync(command, { encoding: 'utf8' });
296
+ // Pass args as an argv array to execFileSync — no shell is spawned, so each
297
+ // argument is delivered to `gh` verbatim. This prevents shell-metacharacter
298
+ // injection (e.g. $(...), backticks, ;, &&, |) from story-derived title/body.
299
+ return execFileSync('gh', args, { encoding: 'utf8' });
298
300
  }
299
301
 
300
302
  /**
@@ -360,8 +362,8 @@ class GitHubProjectsAdapter extends PMAdapter {
360
362
 
361
363
  const result = this._execGH([
362
364
  'issue', 'create',
363
- '--title', `"${title}"`,
364
- '--body', `"${body.replace(/"/g, '\\"')}"`,
365
+ '--title', title,
366
+ '--body', body,
365
367
  '--label', labels.join(','),
366
368
  ]);
367
369
 
@@ -383,8 +385,8 @@ class GitHubProjectsAdapter extends PMAdapter {
383
385
 
384
386
  this._execGH([
385
387
  'issue', 'edit', issueNumber.toString(),
386
- '--title', `"${title}"`,
387
- '--body', `"${body.replace(/"/g, '\\"')}"`,
388
+ '--title', title,
389
+ '--body', body,
388
390
  ]);
389
391
  }
390
392
  }
@@ -0,0 +1,298 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Gemini Commands Sync — generates .gemini/commands/*.toml from parsed agent data.
5
+ *
6
+ * Writes one TOML file per agent plus a sinapse-menu.toml launcher.
7
+ * Called by index.js after agents are parsed; the index.js caller is responsible
8
+ * for registering this as the Gemini IDE target handler.
9
+ *
10
+ * @story 5.1 - IDE Sync Expansion
11
+ */
12
+
13
+ const fs = require('fs-extra');
14
+ const path = require('path');
15
+
16
+ const FALLBACK_DESCRIPTION = 'Agente especializado SINAPSE';
17
+ const MAX_DESCRIPTION_CONTEXT = 120;
18
+
19
+ const MENU_ORDER = [
20
+ 'sinapse-orqx',
21
+ 'analyst',
22
+ 'architect',
23
+ 'data-engineer',
24
+ 'developer',
25
+ 'devops',
26
+ 'project-lead',
27
+ 'product-lead',
28
+ 'quality-gate',
29
+ 'sprint-lead',
30
+ 'squad-creator',
31
+ 'ux-design-expert',
32
+ ];
33
+
34
+ /**
35
+ * Strip the sinapse- prefix so the slug is short and readable.
36
+ * @param {string} agentId
37
+ * @returns {string}
38
+ */
39
+ function commandSlugForAgent(agentId) {
40
+ if (agentId.startsWith('sinapse-')) {
41
+ return agentId.replace(/^sinapse-/, '');
42
+ }
43
+ return agentId;
44
+ }
45
+
46
+ /**
47
+ * Return the Gemini slash-command name for an agent.
48
+ * Example: "developer" → "/sinapse-developer"
49
+ * @param {string} agentId
50
+ * @returns {string}
51
+ */
52
+ function menuCommandName(agentId) {
53
+ return `/sinapse-${commandSlugForAgent(agentId)}`;
54
+ }
55
+
56
+ /**
57
+ * Collapse internal whitespace and trim.
58
+ * @param {string} text
59
+ * @returns {string}
60
+ */
61
+ function normalizeText(text) {
62
+ if (!text || typeof text !== 'string') return '';
63
+ return text.replace(/\s+/g, ' ').trim();
64
+ }
65
+
66
+ /**
67
+ * Truncate to maxLen characters with an ellipsis.
68
+ * @param {string} text
69
+ * @param {number} [maxLen]
70
+ * @returns {string}
71
+ */
72
+ function truncateText(text, maxLen = MAX_DESCRIPTION_CONTEXT) {
73
+ if (!text || text.length <= maxLen) return text;
74
+ return `${text.slice(0, maxLen - 1).trimEnd()}…`;
75
+ }
76
+
77
+ /**
78
+ * Extract a concise one-liner from a whenToUse block.
79
+ * Drops redirect/negative-guidance sections before truncating.
80
+ * @param {string} whenToUse
81
+ * @returns {string}
82
+ */
83
+ function summarizeWhenToUse(whenToUse) {
84
+ const normalized = normalizeText(whenToUse);
85
+ if (!normalized) return '';
86
+
87
+ // Drop redirect/negative guidance sections that are useful for routing, not for menu labels.
88
+ const withoutNegativeSection = normalized.split(/\b(?:NOT\s+for|NÃO\s+para)\b/i)[0].trim();
89
+ const primary = withoutNegativeSection || normalized;
90
+
91
+ // Keep only the first sentence/chunk for concise autocomplete labels.
92
+ const firstChunk = primary.split(/[.;!?](?:\s|$)/)[0].trim();
93
+ return truncateText(firstChunk || primary);
94
+ }
95
+
96
+ /**
97
+ * Escape a string for embedding inside a TOML double-quoted scalar.
98
+ * @param {string} text
99
+ * @returns {string}
100
+ */
101
+ function escapeTomlString(text) {
102
+ return String(text || '').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
103
+ }
104
+
105
+ /**
106
+ * Build a human-readable one-liner description for an agent entry.
107
+ * Prefers title + whenToUse summary; falls back to SINAPSE generic.
108
+ * @param {object} agent - Parsed agent object from agent-parser
109
+ * @returns {string}
110
+ */
111
+ function buildAgentDescription(agent) {
112
+ const agentData = agent.agent || {};
113
+ const title = normalizeText(agentData.title);
114
+ const whenToUseSummary = summarizeWhenToUse(agentData.whenToUse);
115
+
116
+ if (title && whenToUseSummary) {
117
+ return `${title} (${whenToUseSummary})`;
118
+ }
119
+ if (title) {
120
+ return title;
121
+ }
122
+ if (whenToUseSummary) {
123
+ return whenToUseSummary;
124
+ }
125
+ return `Ativar agente SINAPSE ${agent.id}`;
126
+ }
127
+
128
+ /**
129
+ * Build the activation prompt stored in each agent's TOML file.
130
+ * References the agent definition at .gemini/rules/SINAPSE/agents/{id}.md.
131
+ * @param {string} agentId
132
+ * @returns {string}
133
+ */
134
+ function buildAgentCommandPrompt(agentId) {
135
+ return [
136
+ `Ative o agente ${agentId}:`,
137
+ `1. Leia a definição completa em .gemini/rules/SINAPSE/agents/${agentId}.md`,
138
+ '2. Siga as activation-instructions do bloco YAML',
139
+ `3. Renderize o greeting via: node .sinapse-ai/development/scripts/generate-greeting.js ${agentId}`,
140
+ ' Se shell nao disponivel, exiba o greeting de persona_profile.communication.greeting_levels.named',
141
+ '4. Mostre Quick Commands e aguarde input do usuario',
142
+ 'Mantenha a persona até *exit.',
143
+ ].join('\n');
144
+ }
145
+
146
+ /**
147
+ * Build the TOML content for a single agent command file.
148
+ * @param {string} agentId
149
+ * @param {string} [description]
150
+ * @returns {{ filename: string, content: string, agentId: string, description: string }}
151
+ */
152
+ function buildAgentCommandFile(agentId, description = FALLBACK_DESCRIPTION) {
153
+ const slug = commandSlugForAgent(agentId);
154
+
155
+ const prompt = buildAgentCommandPrompt(agentId);
156
+ const content = [
157
+ `description = "${escapeTomlString(description)}"`,
158
+ 'prompt = """',
159
+ prompt,
160
+ '"""',
161
+ '',
162
+ ].join('\n');
163
+
164
+ return {
165
+ filename: `sinapse-${slug}.toml`,
166
+ content,
167
+ agentId,
168
+ description,
169
+ };
170
+ }
171
+
172
+ /**
173
+ * Build the multi-line prompt that lists all agents in the launcher menu.
174
+ * @param {Array<{ agentId: string, description: string }>} commandFiles
175
+ * @returns {string}
176
+ */
177
+ function buildMenuPrompt(commandFiles) {
178
+ const lines = [
179
+ 'Você está no launcher SINAPSE para Gemini.',
180
+ '',
181
+ 'Mostre a lista de agentes abaixo em formato numerado, explicando em 1 linha quando usar cada um:',
182
+ ];
183
+
184
+ let index = 1;
185
+ for (const commandFile of commandFiles) {
186
+ lines.push(`${index}. ${menuCommandName(commandFile.agentId)} - ${commandFile.description}`);
187
+ index += 1;
188
+ }
189
+
190
+ lines.push('');
191
+ lines.push('No final, peça para o usuário escolher um número ou digitar o comando direto.');
192
+ return lines.join('\n');
193
+ }
194
+
195
+ /**
196
+ * Build the sinapse-menu.toml launcher file that lists all agents.
197
+ * @param {Array<{ agentId: string, description: string }>} commandFiles
198
+ * @returns {{ filename: string, content: string }}
199
+ */
200
+ function buildMenuCommandFile(commandFiles) {
201
+ const content = [
202
+ 'description = "Menu rápido SINAPSE (lista agentes e orienta qual ativar)"',
203
+ 'prompt = """',
204
+ buildMenuPrompt(commandFiles),
205
+ '"""',
206
+ '',
207
+ ].join('\n');
208
+
209
+ return {
210
+ filename: 'sinapse-menu.toml',
211
+ content,
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Resolve the display order for agent IDs.
217
+ * MENU_ORDER entries come first (in declared order), extras are sorted alphabetically.
218
+ * @param {string[]} agentIds
219
+ * @returns {string[]}
220
+ */
221
+ function resolveAgentOrder(agentIds) {
222
+ const unique = [...new Set(agentIds)];
223
+ const known = MENU_ORDER.filter((id) => unique.includes(id));
224
+ const extra = unique.filter((id) => !MENU_ORDER.includes(id)).sort();
225
+ return [...known, ...extra];
226
+ }
227
+
228
+ /**
229
+ * Build the full set of TOML command files for all valid agents.
230
+ * Returns an array of { filename, content, agentId, description } entries,
231
+ * with sinapse-menu.toml prepended as the first item.
232
+ * @param {object[]} agents - Array of parsed agent objects from agent-parser
233
+ * @returns {Array<{ filename: string, content: string, agentId: string, description: string }>}
234
+ */
235
+ function buildGeminiCommandFiles(agents) {
236
+ const validAgents = agents
237
+ .filter((agent) => !agent.error)
238
+ .map((agent) => ({
239
+ id: agent.id,
240
+ description: buildAgentDescription(agent),
241
+ }));
242
+
243
+ const ordered = resolveAgentOrder(validAgents.map((agent) => agent.id));
244
+ const byId = new Map(validAgents.map((agent) => [agent.id, agent]));
245
+ const files = ordered.map((id) => {
246
+ const meta = byId.get(id);
247
+ const description = meta?.description || FALLBACK_DESCRIPTION;
248
+ return buildAgentCommandFile(id, description);
249
+ });
250
+ files.unshift(buildMenuCommandFile(files));
251
+ return files;
252
+ }
253
+
254
+ /**
255
+ * Write all Gemini command TOML files to {projectRoot}/.gemini/commands/.
256
+ * In dry-run mode the directory is not created and files are not written,
257
+ * but the return value still lists what would have been written.
258
+ * @param {object[]} agents - Parsed agents
259
+ * @param {string} projectRoot - Absolute path to the project root
260
+ * @param {{ dryRun?: boolean }} [options]
261
+ * @returns {{ commandsDir: string, files: Array<{ filename: string, path: string, content: string }> }}
262
+ */
263
+ function syncGeminiCommands(agents, projectRoot, options = {}) {
264
+ const commandsDir = path.join(projectRoot, '.gemini', 'commands');
265
+ const files = buildGeminiCommandFiles(agents);
266
+ const written = [];
267
+
268
+ if (!options.dryRun) {
269
+ fs.ensureDirSync(commandsDir);
270
+ }
271
+
272
+ for (const file of files) {
273
+ const targetPath = path.join(commandsDir, file.filename);
274
+ if (!options.dryRun) {
275
+ fs.writeFileSync(targetPath, file.content, 'utf8');
276
+ }
277
+ written.push({
278
+ filename: path.join('commands', file.filename),
279
+ path: targetPath,
280
+ content: file.content,
281
+ });
282
+ }
283
+
284
+ return { commandsDir, files: written };
285
+ }
286
+
287
+ module.exports = {
288
+ FALLBACK_DESCRIPTION,
289
+ MENU_ORDER,
290
+ commandSlugForAgent,
291
+ menuCommandName,
292
+ buildAgentDescription,
293
+ summarizeWhenToUse,
294
+ truncateText,
295
+ escapeTomlString,
296
+ buildGeminiCommandFiles,
297
+ syncGeminiCommands,
298
+ };
@@ -24,8 +24,14 @@ const yaml = require('js-yaml');
24
24
  const { parseAllAgents } = require('./agent-parser');
25
25
  const { generateAllRedirects, writeRedirects } = require('./redirect-generator');
26
26
  const { validateAllIdes, formatValidationReport } = require('./validator');
27
+ const { syncGeminiCommands, buildGeminiCommandFiles } = require('./gemini-commands');
28
+
27
29
  // Transformers
28
30
  const claudeCodeTransformer = require('./transformers/claude-code');
31
+ const cursorTransformer = require('./transformers/cursor');
32
+ const antigravityTransformer = require('./transformers/antigravity');
33
+ const githubCopilotTransformer = require('./transformers/github-copilot');
34
+ const kimiTransformer = require(path.resolve(__dirname, 'transformers', 'kimi'));
29
35
 
30
36
  // ANSI colors for output
31
37
  const colors = {
@@ -62,6 +68,31 @@ function loadConfig(projectRoot) {
62
68
  path: '.codex/agents',
63
69
  format: 'full-markdown-yaml',
64
70
  },
71
+ gemini: {
72
+ enabled: true,
73
+ path: '.gemini/rules/SINAPSE/agents',
74
+ format: 'full-markdown-yaml',
75
+ },
76
+ 'github-copilot': {
77
+ enabled: true,
78
+ path: '.github/agents',
79
+ format: 'github-copilot',
80
+ },
81
+ cursor: {
82
+ enabled: true,
83
+ path: '.cursor/rules/agents',
84
+ format: 'condensed-rules',
85
+ },
86
+ antigravity: {
87
+ enabled: true,
88
+ path: '.antigravity/rules/agents',
89
+ format: 'cursor-style',
90
+ },
91
+ kimi: {
92
+ enabled: true,
93
+ path: '.kimi/skills',
94
+ format: 'kimi-skill',
95
+ },
65
96
  },
66
97
  redirects: {
67
98
  'sinapse-developer': 'sinapse-orqx',
@@ -100,9 +131,54 @@ function loadConfig(projectRoot) {
100
131
  function getTransformer(format) {
101
132
  const transformers = {
102
133
  'full-markdown-yaml': claudeCodeTransformer,
134
+ 'condensed-rules': cursorTransformer,
135
+ 'cursor-style': antigravityTransformer,
136
+ 'github-copilot': githubCopilotTransformer,
137
+ 'kimi-skill': kimiTransformer,
103
138
  };
104
139
 
105
- return transformers[format] || claudeCodeTransformer;
140
+ const transformer = transformers[format];
141
+ if (!transformer) {
142
+ throw new Error(
143
+ `No transformer registered for format '${format}'. ` +
144
+ `Register it in getTransformer() before adding a target with this format. ` +
145
+ `Available formats: ${Object.keys(transformers).join(', ')}`
146
+ );
147
+ }
148
+ return transformer;
149
+ }
150
+
151
+ /**
152
+ * Resolve the primary file content for an agent.
153
+ * Allows IDE-specific transform variants (e.g. transformCommand) when present,
154
+ * falling back to the standard transform() otherwise.
155
+ * @param {object} transformer - Transformer module
156
+ * @param {object} agent - Parsed agent data
157
+ * @param {string} ideName - IDE name
158
+ * @returns {string} - Transformed content
159
+ */
160
+ function transformPrimaryContent(transformer, agent, ideName) {
161
+ if (ideName === 'claude-code' && typeof transformer.transformCommand === 'function') {
162
+ return transformer.transformCommand(agent);
163
+ }
164
+ return transformer.transform(agent);
165
+ }
166
+
167
+ /**
168
+ * Guard against path traversal: returns true only when candidatePath resolves
169
+ * to a location strictly inside rootDir.
170
+ * @param {string} rootDir - Allowed root directory
171
+ * @param {string} candidatePath - Path to validate
172
+ * @returns {boolean}
173
+ */
174
+ function isPathInside(rootDir, candidatePath) {
175
+ const relativePath = path.relative(rootDir, candidatePath);
176
+ return (
177
+ relativePath !== '' &&
178
+ relativePath !== '..' &&
179
+ !relativePath.startsWith(`..${path.sep}`) &&
180
+ !path.isAbsolute(relativePath)
181
+ );
106
182
  }
107
183
 
108
184
  /**
@@ -153,9 +229,30 @@ function syncIde(agents, ideConfig, ideName, projectRoot, options) {
153
229
  }
154
230
 
155
231
  try {
156
- const content = transformer.transform(agent);
232
+ const content = transformPrimaryContent(transformer, agent, ideName);
157
233
  const filename = transformer.getFilename(agent);
158
- const targetPath = path.join(result.targetDir, filename);
234
+ const targetRoot = path.resolve(result.targetDir);
235
+
236
+ // Kimi format uses subdirectories per skill: <skill-id>/SKILL.md
237
+ let targetPath;
238
+ if (ideConfig.format === 'kimi-skill' && transformer.getDirname) {
239
+ const dirname = transformer.getDirname(agent);
240
+ const skillDir = path.resolve(targetRoot, dirname);
241
+ targetPath = path.resolve(skillDir, filename);
242
+
243
+ if (!isPathInside(targetRoot, skillDir) || !isPathInside(targetRoot, targetPath)) {
244
+ throw new Error(`Unsafe Kimi output path for agent '${agent.id}'`);
245
+ }
246
+
247
+ if (!options.dryRun) {
248
+ fs.ensureDirSync(skillDir);
249
+ }
250
+ } else {
251
+ targetPath = path.resolve(targetRoot, filename);
252
+ if (!isPathInside(targetRoot, targetPath)) {
253
+ throw new Error(`Unsafe output path for agent '${agent.id}' in ${ideName}`);
254
+ }
255
+ }
159
256
 
160
257
  if (!options.dryRun) {
161
258
  fs.writeFileSync(targetPath, content, 'utf8');
@@ -237,7 +334,13 @@ async function commandSync(options) {
237
334
 
238
335
  const result = syncIde(agents, ideConfig, ideName, projectRoot, options);
239
336
 
240
- result.commandFiles = [];
337
+ // Gemini CLI: also sync slash launcher command files (.gemini/commands/*.toml)
338
+ if (ideName === 'gemini') {
339
+ const geminiCommands = syncGeminiCommands(agents, projectRoot, options);
340
+ result.commandFiles = geminiCommands.files;
341
+ } else {
342
+ result.commandFiles = [];
343
+ }
241
344
 
242
345
  results.push(result);
243
346
 
@@ -339,9 +442,14 @@ async function commandValidate(options) {
339
442
  if (agent.error) continue;
340
443
 
341
444
  try {
342
- const content = transformer.transform(agent);
445
+ const content = transformPrimaryContent(transformer, agent, ideName);
343
446
  const filename = transformer.getFilename(agent);
344
- expectedFiles.push({ filename, content });
447
+ // Kimi format stores each skill in <skill-id>/SKILL.md record nested path
448
+ const relPath =
449
+ ideConfig.format === 'kimi-skill' && transformer.getDirname
450
+ ? path.join(transformer.getDirname(agent), filename)
451
+ : filename;
452
+ expectedFiles.push({ filename: relPath, content });
345
453
  } catch (error) {
346
454
  // Skip agents that fail to transform
347
455
  }
@@ -366,6 +474,17 @@ async function commandValidate(options) {
366
474
  targetDir: path.join(projectRoot, ideConfig.path),
367
475
  };
368
476
 
477
+ // Gemini CLI command launcher files are synced under .gemini/commands/*.toml
478
+ if (ideName === 'gemini') {
479
+ const commandFiles = buildGeminiCommandFiles(agents).map((entry) => ({
480
+ filename: entry.filename,
481
+ content: entry.content,
482
+ }));
483
+ ideConfigs['gemini-commands'] = {
484
+ expectedFiles: commandFiles,
485
+ targetDir: path.join(projectRoot, '.gemini', 'commands'),
486
+ };
487
+ }
369
488
  }
370
489
 
371
490
  // Validate
@@ -486,6 +605,8 @@ if (require.main === module) {
486
605
  module.exports = {
487
606
  loadConfig,
488
607
  getTransformer,
608
+ transformPrimaryContent,
609
+ isPathInside,
489
610
  syncIde,
490
611
  commandSync,
491
612
  commandValidate,