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.
- 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/documentation-first.md +1 -1
- 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/config/merge-utils.js +8 -0
- package/.sinapse-ai/core/errors/constants.js +147 -0
- package/.sinapse-ai/core/errors/error-registry.js +176 -0
- package/.sinapse-ai/core/errors/index.js +50 -0
- package/.sinapse-ai/core/errors/serializer.js +147 -0
- package/.sinapse-ai/core/errors/sinapse-error.js +144 -0
- package/.sinapse-ai/core/errors/utils.js +187 -0
- package/.sinapse-ai/core/execution/build-orchestrator.js +47 -49
- package/.sinapse-ai/core/execution/build-state-manager.js +183 -31
- package/.sinapse-ai/core/execution/parallel-executor.js +7 -1
- package/.sinapse-ai/core/execution/semantic-merge-engine.js +26 -14
- package/.sinapse-ai/core/execution/subagent-dispatcher.js +201 -60
- 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 +212 -107
- 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/memory/gotchas-memory.js +37 -2
- 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/condition-evaluator.js +57 -0
- 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 +150 -10
- package/.sinapse-ai/core/orchestration/parallel-executor.js +6 -1
- package/.sinapse-ai/core/orchestration/recovery-handler.js +81 -8
- package/.sinapse-ai/core/orchestration/workflow-executor.js +41 -0
- package/.sinapse-ai/core/registry/registry-loader.js +71 -5
- package/.sinapse-ai/core/registry/squad-agent-resolver.js +253 -0
- package/.sinapse-ai/core/synapse/context/context-tracker.js +104 -9
- package/.sinapse-ai/core/synapse/context/index.js +19 -0
- package/.sinapse-ai/core/synapse/context/semantic-handshake-engine.js +555 -0
- package/.sinapse-ai/core/synapse/diagnostics/collectors/pipeline-collector.js +4 -2
- package/.sinapse-ai/core/synapse/engine.js +43 -3
- package/.sinapse-ai/core/telemetry/ids-sink.js +188 -0
- package/.sinapse-ai/core/utils/output-formatter.js +8 -290
- package/.sinapse-ai/core/utils/spawn-safe.js +186 -0
- package/.sinapse-ai/core-config.yaml +68 -1
- package/.sinapse-ai/data/entity-registry.yaml +15082 -13618
- package/.sinapse-ai/data/registry-update-log.jsonl +143 -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 +169 -14
- 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 +5 -8
- package/.sinapse-ai/hooks/sinapse-vault-grounding.cjs +6 -9
- 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/integrations/pm-adapters/github-adapter.js +9 -7
- 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 +218 -114
- package/.sinapse-ai/product/templates/engine/renderer.js +20 -1
- 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/docs/framework/collaboration-autonomy-plan.md +18 -18
- package/docs/guides/parallel-workflow.md +6 -6
- package/package.json +22 -5
- 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/doctor/doctor-checks.test.js +44 -22
- 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/regenerate-orqx-stubs.ps1 +6 -5
- 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/squads/claude-code-mastery/knowledge-base/memory-systems-reference.md +1 -1
- package/squads/squad-brand/templates/client-delivery-template.md +1 -1
- package/squads/squad-content/knowledge-base/social-compression-framework.md +1 -1
- package/squads/squad-council/knowledge-base/brand-strategy-models.md +1 -1
- 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
- package/docs/chrome-brain-upgrade-plan.md +0 -624
- package/docs/constitution-compliance.md +0 -87
- package/docs/mega-upgrade-orchestration-plan.md +0 -71
- package/docs/research-synthesis-for-upgrade.md +0 -511
- package/docs/security-audit-report.md +0 -306
|
@@ -9,26 +9,45 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const EventEmitter = require('events');
|
|
12
|
-
const { spawn } = require('child_process');
|
|
13
12
|
const _path = require('path');
|
|
13
|
+
const { runSafe } = require('../utils/spawn-safe');
|
|
14
14
|
|
|
15
|
-
//
|
|
15
|
+
// epic: orchestration-consolidation, F2 — resolves any of the 189 agent ids to
|
|
16
|
+
// its real persona on disk (squads/ + framework agents). Optional: degrades to a
|
|
17
|
+
// generic prompt if the module is missing.
|
|
18
|
+
let SquadAgentResolver;
|
|
19
|
+
try {
|
|
20
|
+
SquadAgentResolver = require('../registry/squad-agent-resolver');
|
|
21
|
+
} catch {
|
|
22
|
+
SquadAgentResolver = null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Import AI Provider Factory (factory module directly — the index barrel
|
|
26
|
+
// requires a removed gemini-provider and would throw on load).
|
|
16
27
|
let AIProviderFactory;
|
|
17
28
|
try {
|
|
18
|
-
AIProviderFactory = require('../../infrastructure/integrations/ai-providers');
|
|
29
|
+
AIProviderFactory = require('../../infrastructure/integrations/ai-providers/ai-provider-factory');
|
|
19
30
|
} catch {
|
|
20
|
-
|
|
31
|
+
try {
|
|
32
|
+
AIProviderFactory = require('../../infrastructure/integrations/ai-providers');
|
|
33
|
+
} catch {
|
|
34
|
+
AIProviderFactory = null;
|
|
35
|
+
}
|
|
21
36
|
}
|
|
22
37
|
|
|
23
38
|
// Import dependencies with fallbacks
|
|
24
39
|
let MemoryQuery, GotchasMemory;
|
|
25
40
|
try {
|
|
26
|
-
|
|
41
|
+
const memoryQueryModule = require('../memory/memory-query');
|
|
42
|
+
MemoryQuery = memoryQueryModule.MemoryQuery || memoryQueryModule;
|
|
43
|
+
if (typeof MemoryQuery !== 'function') MemoryQuery = null;
|
|
27
44
|
} catch {
|
|
28
45
|
MemoryQuery = null;
|
|
29
46
|
}
|
|
30
47
|
try {
|
|
31
|
-
|
|
48
|
+
const gotchasModule = require('../memory/gotchas-memory');
|
|
49
|
+
GotchasMemory = gotchasModule.GotchasMemory || gotchasModule;
|
|
50
|
+
if (typeof GotchasMemory !== 'function') GotchasMemory = null;
|
|
32
51
|
} catch {
|
|
33
52
|
GotchasMemory = null;
|
|
34
53
|
}
|
|
@@ -94,16 +113,42 @@ class SubagentDispatcher extends EventEmitter {
|
|
|
94
113
|
this.maxRetries = config.maxRetries || 2;
|
|
95
114
|
this.retryDelay = config.retryDelay || 2000;
|
|
96
115
|
|
|
97
|
-
//
|
|
98
|
-
this.
|
|
99
|
-
this.gotchasMemory = config.gotchasMemory || (GotchasMemory ? new GotchasMemory() : null);
|
|
116
|
+
// Claude CLI execution timeout (per spawn), 10 min default
|
|
117
|
+
this.claudeTimeout = config.claudeTimeout || 10 * 60 * 1000;
|
|
100
118
|
|
|
101
|
-
// Dispatch log
|
|
119
|
+
// Dispatch log (initialized before optional deps — _tryConstruct logs failures)
|
|
102
120
|
this.dispatchLog = [];
|
|
103
121
|
this.maxLogSize = 100;
|
|
104
122
|
|
|
105
|
-
// Root path for project
|
|
123
|
+
// Root path for project (resolved before memory deps — they need it)
|
|
106
124
|
this.rootPath = config.rootPath || process.cwd();
|
|
125
|
+
|
|
126
|
+
// Agent persona resolver (F2): make every squad/framework agent addressable.
|
|
127
|
+
this.agentResolver =
|
|
128
|
+
config.agentResolver ||
|
|
129
|
+
(SquadAgentResolver ? new SquadAgentResolver(this.rootPath) : null);
|
|
130
|
+
|
|
131
|
+
// Dependencies (never let optional memory enrichment break the dispatcher)
|
|
132
|
+
this.memoryQuery = config.memoryQuery || this._tryConstruct(MemoryQuery, this.rootPath);
|
|
133
|
+
this.gotchasMemory = config.gotchasMemory || this._tryConstruct(GotchasMemory, this.rootPath);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Safely construct an optional dependency. Memory enrichment is best-effort:
|
|
138
|
+
* a broken/missing memory module must never break real agent dispatch.
|
|
139
|
+
* @param {Function|null} Ctor - Constructor (or null when unavailable)
|
|
140
|
+
* @param {string} rootPath - Project root passed to the constructor
|
|
141
|
+
* @returns {Object|null} - Instance or null
|
|
142
|
+
* @private
|
|
143
|
+
*/
|
|
144
|
+
_tryConstruct(Ctor, rootPath) {
|
|
145
|
+
if (typeof Ctor !== 'function') return null;
|
|
146
|
+
try {
|
|
147
|
+
return new Ctor(rootPath);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
this.log?.('optional_dependency_failed', { dep: Ctor.name, error: error.message });
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
107
152
|
}
|
|
108
153
|
|
|
109
154
|
/**
|
|
@@ -134,7 +179,13 @@ class SubagentDispatcher extends EventEmitter {
|
|
|
134
179
|
|
|
135
180
|
// Enrich context
|
|
136
181
|
const enrichedContext = await this.enrichContext(task, context);
|
|
137
|
-
|
|
182
|
+
// Context may contain non-serializable/circular values injected by callers;
|
|
183
|
+
// size accounting is best-effort and must never break dispatch.
|
|
184
|
+
try {
|
|
185
|
+
dispatchRecord.contextSize = JSON.stringify(enrichedContext).length;
|
|
186
|
+
} catch {
|
|
187
|
+
dispatchRecord.contextSize = -1;
|
|
188
|
+
}
|
|
138
189
|
|
|
139
190
|
// Execute with retries
|
|
140
191
|
let lastError = null;
|
|
@@ -211,6 +262,12 @@ class SubagentDispatcher extends EventEmitter {
|
|
|
211
262
|
return this.agentMapping[task.type.toLowerCase()];
|
|
212
263
|
}
|
|
213
264
|
|
|
265
|
+
// F2: if the task names a real agent id directly (any of the 189 squad/
|
|
266
|
+
// framework personas), honor it instead of inferring a generic one.
|
|
267
|
+
if (this.agentResolver && task.name && this.agentResolver.has(task.name)) {
|
|
268
|
+
return `@${this.agentResolver.resolve(task.name).id}`;
|
|
269
|
+
}
|
|
270
|
+
|
|
214
271
|
// Check task tags
|
|
215
272
|
if (task.tags && Array.isArray(task.tags)) {
|
|
216
273
|
for (const tag of task.tags) {
|
|
@@ -303,6 +360,42 @@ class SubagentDispatcher extends EventEmitter {
|
|
|
303
360
|
}
|
|
304
361
|
}
|
|
305
362
|
|
|
363
|
+
/**
|
|
364
|
+
* Resolve the fallback provider name for a given primary.
|
|
365
|
+
* Reads ai_providers.fallback / primary from the factory config instead of
|
|
366
|
+
* the old hardcoded claude<->gemini pair. Falls back safely when config or
|
|
367
|
+
* factory is unavailable.
|
|
368
|
+
* @param {string} primaryName - The provider that just failed/was unavailable.
|
|
369
|
+
* @returns {string|null} - Fallback provider name, or null if none distinct.
|
|
370
|
+
*/
|
|
371
|
+
getFallbackProviderName(primaryName) {
|
|
372
|
+
let config = null;
|
|
373
|
+
if (AIProviderFactory && typeof AIProviderFactory.getConfig === 'function') {
|
|
374
|
+
try {
|
|
375
|
+
config = AIProviderFactory.getConfig();
|
|
376
|
+
} catch (error) {
|
|
377
|
+
this.log('fallback_config_error', { error: error.message });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const providers = config?.ai_providers || {};
|
|
382
|
+
const configuredFallback = providers.fallback;
|
|
383
|
+
const configuredPrimary = providers.primary;
|
|
384
|
+
|
|
385
|
+
// Prefer explicit fallback when it differs from the failing provider.
|
|
386
|
+
if (configuredFallback && configuredFallback !== primaryName) {
|
|
387
|
+
return configuredFallback;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// If the failing provider IS the configured fallback, try the primary.
|
|
391
|
+
if (configuredPrimary && configuredPrimary !== primaryName) {
|
|
392
|
+
return configuredPrimary;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Last-resort safe default: never return the same provider that just failed.
|
|
396
|
+
return primaryName === 'claude' ? null : 'claude';
|
|
397
|
+
}
|
|
398
|
+
|
|
306
399
|
/**
|
|
307
400
|
* Enrich context with memory and gotchas
|
|
308
401
|
* @param {Object} task - Task being dispatched
|
|
@@ -399,11 +492,25 @@ class SubagentDispatcher extends EventEmitter {
|
|
|
399
492
|
|
|
400
493
|
// Try to use AI Provider Factory if available
|
|
401
494
|
if (this.multiProviderEnabled && AIProviderFactory) {
|
|
402
|
-
return this.executeWithProvider(prompt, providerName, task);
|
|
495
|
+
return this.executeWithProvider(prompt, providerName, task, agentId);
|
|
403
496
|
}
|
|
404
497
|
|
|
405
498
|
// Fallback to direct Claude CLI
|
|
406
|
-
return this.executeClaude(prompt);
|
|
499
|
+
return this.executeClaude(prompt, agentId);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Build the env for a spawned agent process. Declares the active agent via
|
|
504
|
+
* SINAPSE_ACTIVE_AGENT so the autonomous path is observable AND the Article
|
|
505
|
+
* VIII delegation hook can enforce when the spawned agent is an orchestrator.
|
|
506
|
+
* (Interactive chat activations don't go through here — they're opt-in.)
|
|
507
|
+
* @param {string} agentId - Resolved agent id (with or without '@')
|
|
508
|
+
* @returns {Object} env additions
|
|
509
|
+
* @private
|
|
510
|
+
*/
|
|
511
|
+
_agentEnv(agentId) {
|
|
512
|
+
const id = String(agentId || '').replace(/^@/, '').trim();
|
|
513
|
+
return id ? { SINAPSE_ACTIVE_AGENT: id } : {};
|
|
407
514
|
}
|
|
408
515
|
|
|
409
516
|
/**
|
|
@@ -413,7 +520,7 @@ class SubagentDispatcher extends EventEmitter {
|
|
|
413
520
|
* @param {Object} task - Original task for context
|
|
414
521
|
* @returns {Promise<Object>} - Execution result
|
|
415
522
|
*/
|
|
416
|
-
async executeWithProvider(prompt, providerName, task) {
|
|
523
|
+
async executeWithProvider(prompt, providerName, task, agentId) {
|
|
417
524
|
const startTime = Date.now();
|
|
418
525
|
|
|
419
526
|
// Get primary provider
|
|
@@ -422,7 +529,7 @@ class SubagentDispatcher extends EventEmitter {
|
|
|
422
529
|
if (!provider) {
|
|
423
530
|
this.log('provider_unavailable', { provider: providerName });
|
|
424
531
|
// Fallback to legacy Claude execution
|
|
425
|
-
return this.executeClaude(prompt);
|
|
532
|
+
return this.executeClaude(prompt, agentId);
|
|
426
533
|
}
|
|
427
534
|
|
|
428
535
|
// Check availability
|
|
@@ -431,21 +538,21 @@ class SubagentDispatcher extends EventEmitter {
|
|
|
431
538
|
if (!isAvailable) {
|
|
432
539
|
this.log('provider_not_available', { provider: providerName });
|
|
433
540
|
|
|
434
|
-
// Try fallback provider
|
|
435
|
-
const fallbackName = providerName
|
|
436
|
-
const fallback = this.getAIProvider(fallbackName);
|
|
541
|
+
// Try fallback provider (config-driven, not hardcoded)
|
|
542
|
+
const fallbackName = this.getFallbackProviderName(providerName);
|
|
543
|
+
const fallback = fallbackName ? this.getAIProvider(fallbackName) : null;
|
|
437
544
|
|
|
438
545
|
if (fallback && (await fallback.checkAvailability())) {
|
|
439
546
|
this.log('using_fallback_provider', { original: providerName, fallback: fallbackName });
|
|
440
|
-
return this.executeWithSingleProvider(fallback, prompt, task);
|
|
547
|
+
return this.executeWithSingleProvider(fallback, prompt, task, agentId);
|
|
441
548
|
}
|
|
442
549
|
|
|
443
550
|
// Last resort: legacy Claude
|
|
444
|
-
return this.executeClaude(prompt);
|
|
551
|
+
return this.executeClaude(prompt, agentId);
|
|
445
552
|
}
|
|
446
553
|
|
|
447
554
|
// Execute with selected provider
|
|
448
|
-
return this.executeWithSingleProvider(provider, prompt, task);
|
|
555
|
+
return this.executeWithSingleProvider(provider, prompt, task, agentId);
|
|
449
556
|
}
|
|
450
557
|
|
|
451
558
|
/**
|
|
@@ -455,10 +562,11 @@ class SubagentDispatcher extends EventEmitter {
|
|
|
455
562
|
* @param {Object} task - Original task
|
|
456
563
|
* @returns {Promise<Object>} - Execution result
|
|
457
564
|
*/
|
|
458
|
-
async executeWithSingleProvider(provider, prompt, task) {
|
|
565
|
+
async executeWithSingleProvider(provider, prompt, task, agentId) {
|
|
459
566
|
try {
|
|
460
567
|
const response = await provider.executeWithRetry(prompt, {
|
|
461
568
|
workingDir: this.rootPath,
|
|
569
|
+
env: this._agentEnv(agentId),
|
|
462
570
|
});
|
|
463
571
|
|
|
464
572
|
this.emit('provider_execution_complete', {
|
|
@@ -591,7 +699,29 @@ class SubagentDispatcher extends EventEmitter {
|
|
|
591
699
|
* @returns {string} - Formatted prompt
|
|
592
700
|
*/
|
|
593
701
|
buildPrompt(agentId, task, context) {
|
|
594
|
-
let prompt =
|
|
702
|
+
let prompt = '';
|
|
703
|
+
|
|
704
|
+
// F2: inject the FULL real persona when the agent is known on disk. This is
|
|
705
|
+
// the difference between "act as a generic dev" and "act as Nimbus, the cloud
|
|
706
|
+
// security engineer with these exact frameworks". Falls back to the one-liner
|
|
707
|
+
// only when the agent is unknown or the resolver is unavailable.
|
|
708
|
+
let personaLoaded = false;
|
|
709
|
+
if (this.agentResolver) {
|
|
710
|
+
const persona = this.agentResolver.loadPersona(agentId);
|
|
711
|
+
if (persona && persona.trim().length > 0) {
|
|
712
|
+
prompt += '## Your Agent Definition (adopt this persona fully)\n\n';
|
|
713
|
+
prompt += persona.trim();
|
|
714
|
+
prompt += '\n\n---\n\n';
|
|
715
|
+
personaLoaded = true;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (!personaLoaded) {
|
|
719
|
+
prompt += `You are ${agentId}, a specialized agent in the SINAPSE framework.\n\n`;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Always state the role label so the dispatched run knows which agent it is,
|
|
723
|
+
// on top of (or instead of) the injected persona.
|
|
724
|
+
prompt += `## Acting as\n${agentId}\n\n`;
|
|
595
725
|
|
|
596
726
|
prompt += '## Task\n';
|
|
597
727
|
prompt += `**ID:** ${task.id}\n`;
|
|
@@ -642,49 +772,60 @@ class SubagentDispatcher extends EventEmitter {
|
|
|
642
772
|
}
|
|
643
773
|
|
|
644
774
|
/**
|
|
645
|
-
* Execute prompt via Claude CLI
|
|
775
|
+
* Execute prompt via Claude CLI.
|
|
776
|
+
*
|
|
777
|
+
* Hardened: the prompt is delivered through stdin (never the command line)
|
|
778
|
+
* and the process is spawned by argv via cross-spawn — so the prompt can
|
|
779
|
+
* contain quotes, pipes, `;`, `$()` or any shell metacharacter without ever
|
|
780
|
+
* being interpreted as a command (shell-injection is structurally impossible).
|
|
781
|
+
* cross-spawn also resolves `claude.cmd` on Windows (native spawn → ENOENT).
|
|
782
|
+
*
|
|
646
783
|
* @param {string} prompt - Prompt to execute
|
|
647
|
-
* @returns {Promise<Object>} -
|
|
784
|
+
* @returns {Promise<Object>} - { success, output, filesModified }
|
|
648
785
|
*/
|
|
649
|
-
executeClaude(prompt) {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
const fullCommand = `echo '${escapedPrompt}' | claude ${args.join(' ')}`;
|
|
654
|
-
|
|
655
|
-
const child = spawn('sh', ['-c', fullCommand], {
|
|
656
|
-
cwd: this.rootPath,
|
|
657
|
-
env: { ...process.env },
|
|
658
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
659
|
-
});
|
|
786
|
+
async executeClaude(prompt, agentId) {
|
|
787
|
+
if (!prompt || typeof prompt !== 'string') {
|
|
788
|
+
throw new Error('executeClaude requires a non-empty string prompt');
|
|
789
|
+
}
|
|
660
790
|
|
|
661
|
-
|
|
662
|
-
let stderr = '';
|
|
791
|
+
const args = ['--print', '--dangerously-skip-permissions'];
|
|
663
792
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}
|
|
793
|
+
const result = await runSafe('claude', args, {
|
|
794
|
+
cwd: this.rootPath,
|
|
795
|
+
env: { ...process.env, ...this._agentEnv(agentId) },
|
|
796
|
+
timeout: this.claudeTimeout,
|
|
797
|
+
input: prompt,
|
|
798
|
+
});
|
|
667
799
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
});
|
|
800
|
+
const stdout = (result.stdout || '').trim();
|
|
801
|
+
const stderr = (result.stderr || '').trim();
|
|
671
802
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
} else {
|
|
680
|
-
reject(new Error(`Claude CLI exited with code ${code}: ${stderr || stdout}`));
|
|
681
|
-
}
|
|
682
|
-
});
|
|
803
|
+
if (result.success) {
|
|
804
|
+
return {
|
|
805
|
+
success: true,
|
|
806
|
+
output: stdout,
|
|
807
|
+
filesModified: this.extractModifiedFiles(stdout),
|
|
808
|
+
};
|
|
809
|
+
}
|
|
683
810
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
811
|
+
if (result.signal) {
|
|
812
|
+
throw new Error(`Claude CLI killed by signal ${result.signal}: ${stderr || stdout}`);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// User-environment hooks (SessionEnd etc.) can fail AFTER the model already
|
|
816
|
+
// printed its full response, poisoning the exit code. In --print mode a
|
|
817
|
+
// non-empty stdout + hook-related stderr means the work was done — accept it
|
|
818
|
+
// instead of discarding paid output and retrying.
|
|
819
|
+
if (stdout.length > 0 && /hook/i.test(stderr)) {
|
|
820
|
+
this.log('exit_code_poisoned_by_hook', { code: result.code, stderr: stderr.slice(0, 200) });
|
|
821
|
+
return {
|
|
822
|
+
success: true,
|
|
823
|
+
output: stdout,
|
|
824
|
+
filesModified: this.extractModifiedFiles(stdout),
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
throw new Error(`Claude CLI exited with code ${result.code}: ${stderr || stdout}`);
|
|
688
829
|
}
|
|
689
830
|
|
|
690
831
|
/**
|
|
@@ -12,7 +12,10 @@ const _path = require('path');
|
|
|
12
12
|
// Import dependencies with fallbacks
|
|
13
13
|
let WaveAnalyzer;
|
|
14
14
|
try {
|
|
15
|
-
WaveAnalyzer
|
|
15
|
+
// Named export — the module exports { WaveAnalyzer, ... }. Requiring the
|
|
16
|
+
// whole object and calling `new WaveAnalyzer()` (line ~37) would throw
|
|
17
|
+
// "not a constructor". Destructure the class.
|
|
18
|
+
({ WaveAnalyzer } = require('../../workflow-intelligence/engine/wave-analyzer'));
|
|
16
19
|
} catch {
|
|
17
20
|
WaveAnalyzer = null;
|
|
18
21
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Grounding Hooks (Story 10.47)
|
|
1
|
+
# Grounding Hooks (Story 10.47 + GA-1.6)
|
|
2
2
|
|
|
3
3
|
Hooks shipped by SINAPSE-AI that read user-supplied grounding sources and
|
|
4
4
|
inject relevant context into agent prompts. Each hook is **opt-in**:
|
|
@@ -6,18 +6,78 @@ absence of `~/.claude/sinapse-ai-config.yaml` or `enabled: false` in the
|
|
|
6
6
|
matching section means the hook is a complete no-op (no I/O, no warnings,
|
|
7
7
|
no errors).
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
## Architecture: Two layers
|
|
10
|
+
|
|
11
|
+
### Layer 1 — Executable hooks (`.sinapse-ai/hooks/`, story GA-1.6) ✅ ACTIVE
|
|
12
|
+
|
|
13
|
+
These are the hooks Claude Code actually runs via `UserPromptSubmit`. They
|
|
14
|
+
read real files and inject context into prompts. Registered automatically
|
|
15
|
+
during `npx sinapse-ai install` into `~/.claude/settings.json`.
|
|
16
|
+
|
|
17
|
+
| Executable hook | Reads | Injects |
|
|
18
|
+
|-----------------|-------|---------|
|
|
19
|
+
| `sinapse-vault-grounding.cjs` | `grounding.vault.path` → top-5 `.md` notes | `<vault-grounding>` |
|
|
20
|
+
| `sinapse-ds-grounding.cjs` | `grounding.designSystem.rootPath` → DS law file | `<ds-grounding>` |
|
|
21
|
+
| `sinapse-brand-grounding.cjs` | `grounding.brand.brandbookPath` → brandbook | `<brand-grounding>` |
|
|
22
|
+
|
|
23
|
+
**Caller:** `bin/lib/register-grounding-hooks.js` (invoked by `bin/commands/install.js`
|
|
24
|
+
phase 6b). Hook registration is idempotent and non-destructive.
|
|
25
|
+
|
|
26
|
+
### Layer 2 — Library resolvers (`core/grounding/`, story 10.47) — DEFER
|
|
27
|
+
|
|
28
|
+
These library stubs (`vault.cjs`, `design-system.cjs`, `brand.cjs`) are
|
|
29
|
+
shallow wrappers around `config-loader.cjs`. They return a structured envelope
|
|
30
|
+
describing what is configured, but do **not** inject context into prompts.
|
|
31
|
+
|
|
32
|
+
**Why defer:** The executable hooks in Layer 1 (GA-1.6) already implement
|
|
33
|
+
the concrete content injection logic directly. The library resolvers were
|
|
34
|
+
designed as the "integration point" for future use cases where something
|
|
35
|
+
other than the Claude Code hook chain needs to consume grounding data
|
|
36
|
+
(e.g., a CLI command, a report generator, or a programmatic API). No such
|
|
37
|
+
consumer exists today.
|
|
38
|
+
|
|
39
|
+
**Trigger to activate library resolvers:** a concrete caller (e.g., a new
|
|
40
|
+
CLI sub-command `sinapse grounding status`, or a report generator) that
|
|
41
|
+
needs to query grounding config programmatically. At that point, extend
|
|
42
|
+
`vault.cjs` / `design-system.cjs` / `brand.cjs` to read actual files and
|
|
43
|
+
return rich structured data. Do **not** create a caller just to justify
|
|
44
|
+
activating these resolvers (Article XI — no orphans).
|
|
45
|
+
|
|
46
|
+
**Current callers of library resolvers:** `bin/lib/prompts.js` and
|
|
47
|
+
`bin/commands/update.js` import `grounding-config` (the wizard package, not
|
|
48
|
+
these library hooks). The library hooks themselves have no production caller
|
|
49
|
+
beyond their own test suite.
|
|
50
|
+
|
|
51
|
+
| Library resolver | Status | Defer trigger |
|
|
52
|
+
|-----------------|--------|---------------|
|
|
53
|
+
| `vault.cjs` | stub — returns config envelope | new CLI/API consumer |
|
|
54
|
+
| `design-system.cjs` | stub — returns config envelope | new CLI/API consumer |
|
|
55
|
+
| `brand.cjs` | stub — returns config envelope | new CLI/API consumer |
|
|
56
|
+
| `config-loader.cjs` | functional — reads YAML | used by all three stubs |
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Configuration
|
|
14
61
|
|
|
15
62
|
Configure interactively via `npx sinapse-ai install` (Story 10.46+10.47
|
|
16
63
|
wizard) or by editing the YAML file directly. See
|
|
17
64
|
`docs/guides/grounding-setup.md` for the full guide.
|
|
18
65
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
66
|
+
`~/.claude/sinapse-ai-config.yaml` schema:
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
version: '1'
|
|
70
|
+
grounding:
|
|
71
|
+
vault:
|
|
72
|
+
enabled: true
|
|
73
|
+
path: /abs/path/to/vault
|
|
74
|
+
domains: [sinapse, personal]
|
|
75
|
+
designSystem:
|
|
76
|
+
enabled: true
|
|
77
|
+
profileName: SINAPSE
|
|
78
|
+
rootPath: /abs/path/to/brandbook
|
|
79
|
+
brand:
|
|
80
|
+
enabled: true
|
|
81
|
+
profileName: SINAPSE
|
|
82
|
+
brandbookPath: /abs/path/to/brandbook/0.0-guidelines.md
|
|
83
|
+
```
|
|
@@ -45,7 +45,7 @@ class FrameworkConfigCheck extends BaseCheck {
|
|
|
45
45
|
severity: CheckSeverity.HIGH,
|
|
46
46
|
timeout: 3000,
|
|
47
47
|
cacheable: true,
|
|
48
|
-
healingTier:
|
|
48
|
+
healingTier: 1, // Can auto-create .sinapse local config dir
|
|
49
49
|
tags: ['sinapse', 'config', 'framework'],
|
|
50
50
|
});
|
|
51
51
|
}
|
|
@@ -111,7 +111,9 @@ class FrameworkConfigCheck extends BaseCheck {
|
|
|
111
111
|
if (missingRecommended.length > 0) {
|
|
112
112
|
const missing = missingRecommended.map((c) => c.path).join(', ');
|
|
113
113
|
return this.warning(`Missing recommended configuration: ${missing}`, {
|
|
114
|
-
recommendation: '
|
|
114
|
+
recommendation: 'Run health check with --fix to create missing recommended configuration',
|
|
115
|
+
healable: true,
|
|
116
|
+
healingTier: 1,
|
|
115
117
|
details: {
|
|
116
118
|
missingRecommended: missingRecommended.map((c) => ({
|
|
117
119
|
path: c.path,
|
|
@@ -126,6 +128,40 @@ class FrameworkConfigCheck extends BaseCheck {
|
|
|
126
128
|
details: { found: found.map((c) => c.path) },
|
|
127
129
|
});
|
|
128
130
|
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get healer for this check
|
|
134
|
+
* @returns {Object} Healer configuration
|
|
135
|
+
*/
|
|
136
|
+
getHealer() {
|
|
137
|
+
return {
|
|
138
|
+
name: 'create-sinapse-local-config',
|
|
139
|
+
action: 'create-config',
|
|
140
|
+
successMessage: 'Created .sinapse local configuration directory',
|
|
141
|
+
fix: async (_result) => {
|
|
142
|
+
const projectRoot = process.cwd();
|
|
143
|
+
const sinapseDir = path.join(projectRoot, '.sinapse');
|
|
144
|
+
|
|
145
|
+
await fs.mkdir(sinapseDir, { recursive: true });
|
|
146
|
+
|
|
147
|
+
const configPath = path.join(sinapseDir, 'config.yaml');
|
|
148
|
+
try {
|
|
149
|
+
await fs.access(configPath);
|
|
150
|
+
} catch {
|
|
151
|
+
// Create minimal config stub if it doesn't exist
|
|
152
|
+
const stub = [
|
|
153
|
+
'# SINAPSE local configuration',
|
|
154
|
+
'# Generated by SINAPSE Health Check auto-heal',
|
|
155
|
+
'permissions:',
|
|
156
|
+
' mode: ask',
|
|
157
|
+
].join('\n') + '\n';
|
|
158
|
+
await fs.writeFile(configPath, stub, 'utf8');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return { success: true, message: 'Created .sinapse/config.yaml' };
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
129
165
|
}
|
|
130
166
|
|
|
131
167
|
module.exports = FrameworkConfigCheck;
|
|
@@ -28,7 +28,7 @@ class PackageJsonCheck extends BaseCheck {
|
|
|
28
28
|
severity: CheckSeverity.CRITICAL,
|
|
29
29
|
timeout: 2000,
|
|
30
30
|
cacheable: true,
|
|
31
|
-
healingTier:
|
|
31
|
+
healingTier: 1, // Can auto-patch cosmetic fields (name/version stubs) when JSON is valid
|
|
32
32
|
tags: ['npm', 'config', 'required'],
|
|
33
33
|
});
|
|
34
34
|
}
|
|
@@ -70,8 +70,10 @@ class PackageJsonCheck extends BaseCheck {
|
|
|
70
70
|
|
|
71
71
|
if (issues.length > 0) {
|
|
72
72
|
return this.warning(`package.json has issues: ${issues.join(', ')}`, {
|
|
73
|
-
recommendation: '
|
|
74
|
-
|
|
73
|
+
recommendation: 'Run health check with --fix to patch missing fields',
|
|
74
|
+
healable: true,
|
|
75
|
+
healingTier: 1,
|
|
76
|
+
details: { issues, path: packagePath, packagePath },
|
|
75
77
|
});
|
|
76
78
|
}
|
|
77
79
|
|
|
@@ -100,6 +102,48 @@ class PackageJsonCheck extends BaseCheck {
|
|
|
100
102
|
return this.error(`Failed to read package.json: ${error.message}`, error);
|
|
101
103
|
}
|
|
102
104
|
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get healer for this check
|
|
108
|
+
* Patches stub name/version fields when JSON is valid but incomplete
|
|
109
|
+
* @returns {Object} Healer configuration
|
|
110
|
+
*/
|
|
111
|
+
getHealer() {
|
|
112
|
+
return {
|
|
113
|
+
name: 'patch-package-json-fields',
|
|
114
|
+
action: 'patch-fields',
|
|
115
|
+
successMessage: 'Patched missing package.json fields with safe defaults',
|
|
116
|
+
targetFile: 'package.json',
|
|
117
|
+
fix: async (_result) => {
|
|
118
|
+
const projectRoot = process.cwd();
|
|
119
|
+
const packagePath = path.join(projectRoot, 'package.json');
|
|
120
|
+
|
|
121
|
+
const content = await fs.readFile(packagePath, 'utf8');
|
|
122
|
+
const packageJson = JSON.parse(content);
|
|
123
|
+
|
|
124
|
+
let patched = false;
|
|
125
|
+
|
|
126
|
+
if (!packageJson.name) {
|
|
127
|
+
packageJson.name = path.basename(projectRoot).toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
128
|
+
patched = true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!packageJson.version) {
|
|
132
|
+
packageJson.version = '0.1.0';
|
|
133
|
+
patched = true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (patched) {
|
|
137
|
+
await fs.writeFile(packagePath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
success: true,
|
|
142
|
+
message: patched ? 'Patched package.json with stub fields' : 'No patches needed',
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
103
147
|
}
|
|
104
148
|
|
|
105
149
|
module.exports = PackageJsonCheck;
|