sinapse-ai 1.7.0 → 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.
- package/.claude/CLAUDE.md +5 -11
- package/.claude/hooks/README.md +14 -1
- package/.claude/hooks/code-intel-pretool.cjs +115 -0
- package/.claude/hooks/enforce-delegation.cjs +31 -3
- package/.claude/hooks/enforce-framework-boundary.cjs +324 -0
- package/.claude/hooks/enforce-permission-mode.cjs +249 -0
- package/.claude/hooks/secret-scanning.cjs +34 -43
- package/.claude/hooks/synapse-engine.cjs +23 -23
- package/.claude/hooks/telemetry-post-tool.cjs +128 -0
- package/.claude/hooks/telemetry-stop.cjs +132 -0
- package/.claude/hooks/verify-packages.cjs +9 -2
- package/.claude/rules/hook-governance.md +2 -0
- package/.sinapse-ai/cli/commands/health/index.js +24 -0
- package/.sinapse-ai/core/README.md +11 -0
- package/.sinapse-ai/core/config/config-loader.js +19 -0
- package/.sinapse-ai/core/execution/build-orchestrator.js +4 -1
- package/.sinapse-ai/core/execution/parallel-executor.js +7 -1
- package/.sinapse-ai/core/execution/subagent-dispatcher.js +126 -28
- package/.sinapse-ai/core/execution/wave-executor.js +4 -1
- package/.sinapse-ai/core/grounding/README.md +71 -11
- package/.sinapse-ai/core/health-check/checks/project/framework-config.js +38 -2
- package/.sinapse-ai/core/health-check/checks/project/package-json.js +47 -3
- package/.sinapse-ai/core/health-check/checks/services/gemini-cli.js +117 -0
- package/.sinapse-ai/core/health-check/checks/services/index.js +2 -0
- package/.sinapse-ai/core/health-check/healers/index.js +40 -3
- package/.sinapse-ai/core/ideation/ideation-engine.js +170 -121
- package/.sinapse-ai/core/ids/gate-evaluator.js +318 -0
- package/.sinapse-ai/core/ids/gates/g5-semantic-handshake.js +190 -0
- package/.sinapse-ai/core/ids/gates/g6-ci-integrity.js +162 -0
- package/.sinapse-ai/core/ids/index.js +30 -0
- package/.sinapse-ai/core/memory/__tests__/active-modules.verify.js +11 -0
- package/.sinapse-ai/core/orchestration/agent-invoker.js +29 -6
- package/.sinapse-ai/core/orchestration/brownfield-handler.js +36 -3
- package/.sinapse-ai/core/orchestration/executors/epic-3-executor.js +76 -5
- package/.sinapse-ai/core/orchestration/executors/epic-4-executor.js +63 -17
- package/.sinapse-ai/core/orchestration/executors/epic-6-executor.js +153 -41
- package/.sinapse-ai/core/orchestration/executors/epic-executor.js +40 -0
- package/.sinapse-ai/core/orchestration/greenfield-handler.js +87 -3
- package/.sinapse-ai/core/orchestration/master-orchestrator.js +105 -7
- package/.sinapse-ai/core/orchestration/parallel-executor.js +6 -1
- package/.sinapse-ai/core/orchestration/workflow-executor.js +41 -0
- package/.sinapse-ai/core/registry/squad-agent-resolver.js +253 -0
- package/.sinapse-ai/core/telemetry/ids-sink.js +188 -0
- package/.sinapse-ai/core/utils/output-formatter.js +8 -290
- package/.sinapse-ai/core-config.yaml +49 -1
- package/.sinapse-ai/data/entity-registry.yaml +15081 -13735
- package/.sinapse-ai/data/registry-update-log.jsonl +86 -0
- package/.sinapse-ai/development/agents/developer.md +2 -0
- package/.sinapse-ai/development/agents/devops.md +9 -0
- package/.sinapse-ai/development/external-executors/README.md +18 -0
- package/.sinapse-ai/development/external-executors/codex.md +56 -0
- package/.sinapse-ai/development/scripts/populate-entity-registry.js +65 -9
- package/.sinapse-ai/development/scripts/squad/squad-downloader.js +54 -11
- package/.sinapse-ai/development/tasks/delegate-to-external-executor.md +152 -0
- package/.sinapse-ai/development/tasks/github-devops-pre-push-quality-gate.md +46 -29
- package/.sinapse-ai/development/tasks/update-sinapse.md +3 -3
- package/.sinapse-ai/hooks/sinapse-brand-grounding.cjs +4 -7
- package/.sinapse-ai/hooks/sinapse-ds-grounding.cjs +4 -7
- package/.sinapse-ai/hooks/sinapse-vault-grounding.cjs +4 -7
- package/.sinapse-ai/infrastructure/integrations/ai-providers/ai-provider-factory.js +4 -1
- package/.sinapse-ai/infrastructure/integrations/ai-providers/claude-provider.js +57 -55
- package/.sinapse-ai/infrastructure/scripts/ide-sync/gemini-commands.js +298 -0
- package/.sinapse-ai/infrastructure/scripts/ide-sync/index.js +127 -6
- package/.sinapse-ai/infrastructure/scripts/ide-sync/persona-renderer.js +97 -0
- package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/antigravity.js +121 -0
- package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/cursor.js +119 -0
- package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/github-copilot.js +191 -0
- package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/kimi.js +448 -0
- package/.sinapse-ai/install-manifest.yaml +158 -90
- package/.sinapse-ai/scripts/pm.sh +18 -6
- package/bin/cli.js +17 -0
- package/bin/commands/agents.js +96 -0
- package/bin/commands/doctor.js +15 -0
- package/bin/commands/ideate.js +129 -0
- package/bin/commands/uninstall.js +40 -0
- package/bin/postinstall.js +50 -4
- package/bin/sinapse.js +146 -2
- package/bin/utils/secret-scanner-core.js +253 -0
- package/bin/utils/staged-secret-scan.js +106 -40
- package/package.json +13 -3
- package/packages/installer/src/installer/git-hooks-installer.js +384 -0
- package/packages/installer/src/installer/sinapse-ai-installer.js +16 -0
- package/packages/installer/src/wizard/ide-config-generator.js +23 -0
- package/packages/installer/src/wizard/validators.js +38 -1
- package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +5 -1
- package/packages/installer/tests/unit/git-hooks-installer.test.js +262 -0
- package/scripts/eval-runner.js +422 -0
- package/scripts/generate-install-manifest.js +13 -9
- package/scripts/generate-synapse-runtime.js +51 -0
- package/scripts/validate-all.js +1 -0
- package/scripts/validate-evals.js +466 -0
- package/scripts/validate-schemas.js +539 -0
- package/scripts/validate-squad-orqx.js +9 -2
- package/.sinapse-ai/development/scripts/elicitation-engine.js +0 -385
- package/.sinapse-ai/development/scripts/elicitation-session-manager.js +0 -300
- package/.sinapse-ai/development/tasks/test-validation-task.md +0 -172
|
@@ -318,45 +318,64 @@ If conflicts detected, fail with message:
|
|
|
318
318
|
Resolve conflicts before pushing.
|
|
319
319
|
```
|
|
320
320
|
|
|
321
|
-
### 4. Run
|
|
321
|
+
### 4. Run Layer 1 Quality Gate (lint + test + typecheck) — CANONICAL
|
|
322
322
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
323
|
+
> **Do NOT reimplement lint/test/typecheck here.** The framework already ships
|
|
324
|
+
> the canonical Layer 1 pre-commit gate (`core/quality-gates/layer1-precommit.js`),
|
|
325
|
+
> exposed via the CLI. Calling it keeps a single source of truth for the quality
|
|
326
|
+
> checks (Constitution Art. I — CLI First) instead of forking the logic.
|
|
327
327
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
328
|
+
```bash
|
|
329
|
+
sinapse qa run --layer=1
|
|
330
|
+
```
|
|
332
331
|
|
|
332
|
+
Layer 1 runs **lint (ESLint), unit tests (Jest), and typecheck** — fast local
|
|
333
|
+
checks — and gracefully skips any check whose npm script is absent. Exit code:
|
|
334
|
+
`0` = PASS, non-zero = FAIL.
|
|
335
|
+
|
|
336
|
+
```javascript
|
|
337
|
+
const { execSync } = require('child_process');
|
|
338
|
+
|
|
339
|
+
function runLayer1QualityGate(projectRoot) {
|
|
333
340
|
try {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
});
|
|
338
|
-
console.log(`✓ ${scriptName} PASSED`);
|
|
341
|
+
// The canonical 3-check Layer 1 gate. stdio:'inherit' streams its report.
|
|
342
|
+
execSync('sinapse qa run --layer=1', { cwd: projectRoot, stdio: 'inherit' });
|
|
343
|
+
console.log('✓ Layer 1 (lint + test + typecheck) PASSED');
|
|
339
344
|
return { passed: true };
|
|
340
345
|
} catch (error) {
|
|
341
|
-
console.error(
|
|
346
|
+
console.error('❌ Layer 1 quality gate FAILED');
|
|
342
347
|
return { passed: false, error };
|
|
343
348
|
}
|
|
344
349
|
}
|
|
345
350
|
```
|
|
346
351
|
|
|
347
|
-
### 5. Run npm
|
|
352
|
+
### 5. Run npm run build (if script exists)
|
|
348
353
|
|
|
349
|
-
|
|
354
|
+
Build is outside Layer 1's scope (Layer 1 is the fast lint/test/typecheck pass),
|
|
355
|
+
so it stays a separate step. Skips gracefully when the `build` script is absent.
|
|
350
356
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
357
|
+
```javascript
|
|
358
|
+
function runBuild(projectRoot) {
|
|
359
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
360
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
354
361
|
|
|
355
|
-
|
|
362
|
+
if (!packageJson.scripts || !packageJson.scripts.build) {
|
|
363
|
+
console.log('⚠️ Script "build" not found - skipping');
|
|
364
|
+
return { skipped: true };
|
|
365
|
+
}
|
|
356
366
|
|
|
357
|
-
|
|
367
|
+
try {
|
|
368
|
+
execSync('npm run build', { cwd: projectRoot, stdio: 'inherit' });
|
|
369
|
+
console.log('✓ build PASSED');
|
|
370
|
+
return { passed: true };
|
|
371
|
+
} catch (error) {
|
|
372
|
+
console.error('❌ build FAILED');
|
|
373
|
+
return { passed: false, error };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
```
|
|
358
377
|
|
|
359
|
-
###
|
|
378
|
+
### 6. Run CodeRabbit CLI Review (TR-3.14.12)
|
|
360
379
|
|
|
361
380
|
```javascript
|
|
362
381
|
const { execSync } = require('child_process');
|
|
@@ -503,7 +522,7 @@ if (coderabbitResult.gateImpact === 'CONCERNS') {
|
|
|
503
522
|
}
|
|
504
523
|
```
|
|
505
524
|
|
|
506
|
-
###
|
|
525
|
+
### 7. Run Security Scan (TR-3.14.11)
|
|
507
526
|
|
|
508
527
|
```javascript
|
|
509
528
|
const { execSync } = require('child_process');
|
|
@@ -630,7 +649,7 @@ function determineSecurityGate(results) {
|
|
|
630
649
|
}
|
|
631
650
|
```
|
|
632
651
|
|
|
633
|
-
###
|
|
652
|
+
### 7.1 Impact Analysis (Code Intelligence — Advisory Only)
|
|
634
653
|
|
|
635
654
|
> **Added by:** Story NOG-7 (DevOps Pre-Push Impact Analysis)
|
|
636
655
|
> **Behavior:** Advisory only — NEVER blocks push. Auto-skips if code intelligence unavailable.
|
|
@@ -688,7 +707,7 @@ Impact Analysis:
|
|
|
688
707
|
|
|
689
708
|
---
|
|
690
709
|
|
|
691
|
-
###
|
|
710
|
+
### 8. Verify Story Status (Optional - if using story-driven workflow)
|
|
692
711
|
|
|
693
712
|
```javascript
|
|
694
713
|
function checkStoryStatus(storyPath) {
|
|
@@ -735,9 +754,7 @@ Mode: {framework-development | project-development}
|
|
|
735
754
|
Quality Checks:
|
|
736
755
|
✓ No uncommitted changes
|
|
737
756
|
✓ No merge conflicts
|
|
738
|
-
✓
|
|
739
|
-
✓ npm test PASSED
|
|
740
|
-
✓ npm run typecheck PASSED
|
|
757
|
+
✓ Layer 1 (lint+test+typecheck) PASSED (via `sinapse qa run --layer=1`)
|
|
741
758
|
✓ npm run build PASSED
|
|
742
759
|
✓ Security scan PASSED
|
|
743
760
|
⚠️ Story status SKIPPED (no story file)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Task: Update SINAPSE Framework
|
|
2
2
|
|
|
3
|
-
> **Version:**
|
|
3
|
+
> **Version:** 5.2.0
|
|
4
4
|
> **Created:** 2026-01-29
|
|
5
|
-
> **Updated:** 2026-
|
|
5
|
+
> **Updated:** 2026-06-15
|
|
6
6
|
> **Type:** SYNC (git-native framework synchronization)
|
|
7
7
|
> **Agent:** @devops (Pipeline) or @sinapse (Orion)
|
|
8
|
-
> **Execution:**
|
|
8
|
+
> **Execution:** Bash script (~150 lines)
|
|
9
9
|
|
|
10
10
|
## Purpose
|
|
11
11
|
|
|
@@ -39,15 +39,12 @@ function readStdin() {
|
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// Story 10.47: delegate to the shared grounding config loader instead of
|
|
43
|
+
// duplicating the read+parse. The require is guarded so the hook stays
|
|
44
|
+
// fail-open even if the shared module is somehow absent at runtime.
|
|
42
45
|
function loadConfig() {
|
|
43
46
|
try {
|
|
44
|
-
|
|
45
|
-
const raw = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
46
|
-
let yaml;
|
|
47
|
-
try { yaml = require('js-yaml'); } catch { return null; }
|
|
48
|
-
const parsed = yaml.load(raw);
|
|
49
|
-
if (!parsed || typeof parsed !== 'object') return null;
|
|
50
|
-
return parsed;
|
|
47
|
+
return require('../core/grounding/config-loader.cjs').loadGroundingConfig(CONFIG_PATH);
|
|
51
48
|
} catch {
|
|
52
49
|
return null;
|
|
53
50
|
}
|
|
@@ -66,15 +66,12 @@ function readStdin() {
|
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
// Story 10.47: delegate to the shared grounding config loader instead of
|
|
70
|
+
// duplicating the read+parse. The require is guarded so the hook stays
|
|
71
|
+
// fail-open even if the shared module is somehow absent at runtime.
|
|
69
72
|
function loadConfig() {
|
|
70
73
|
try {
|
|
71
|
-
|
|
72
|
-
const raw = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
73
|
-
let yaml;
|
|
74
|
-
try { yaml = require('js-yaml'); } catch { return null; }
|
|
75
|
-
const parsed = yaml.load(raw);
|
|
76
|
-
if (!parsed || typeof parsed !== 'object') return null;
|
|
77
|
-
return parsed;
|
|
74
|
+
return require('../core/grounding/config-loader.cjs').loadGroundingConfig(CONFIG_PATH);
|
|
78
75
|
} catch {
|
|
79
76
|
return null;
|
|
80
77
|
}
|
|
@@ -54,15 +54,12 @@ function readStdin() {
|
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// Story 10.47: delegate to the shared grounding config loader instead of
|
|
58
|
+
// duplicating the read+parse. The require is guarded so the hook stays
|
|
59
|
+
// fail-open even if the shared module is somehow absent at runtime.
|
|
57
60
|
function loadConfig() {
|
|
58
61
|
try {
|
|
59
|
-
|
|
60
|
-
const raw = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
61
|
-
let yaml;
|
|
62
|
-
try { yaml = require('js-yaml'); } catch { return null; }
|
|
63
|
-
const parsed = yaml.load(raw);
|
|
64
|
-
if (!parsed || typeof parsed !== 'object') return null;
|
|
65
|
-
return parsed;
|
|
62
|
+
return require('../core/grounding/config-loader.cjs').loadGroundingConfig(CONFIG_PATH);
|
|
66
63
|
} catch {
|
|
67
64
|
return null;
|
|
68
65
|
}
|
|
@@ -42,7 +42,10 @@ const DEFAULT_CONFIG = {
|
|
|
42
42
|
},
|
|
43
43
|
},
|
|
44
44
|
claude: {
|
|
45
|
-
model:
|
|
45
|
+
// No hardcoded model: stale IDs get rejected by the CLI ("model may not
|
|
46
|
+
// exist"). null → omit --model and use the user's CLI default, which is
|
|
47
|
+
// always a valid, current model.
|
|
48
|
+
model: null,
|
|
46
49
|
timeout: 300000,
|
|
47
50
|
dangerouslySkipPermissions: false,
|
|
48
51
|
},
|
|
@@ -7,8 +7,11 @@
|
|
|
7
7
|
* @see Epic GEMINI-INT - Story 2: AI Provider Factory Pattern
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
const {
|
|
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
|
|
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:
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
/**
|
|
@@ -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
|
+
};
|