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
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GateEvaluator — IDS Gate Wiring (Frente 4.0)
|
|
5
|
+
*
|
|
6
|
+
* Bridges the inert verification gates (G1-G5) to the live SDC workflow.
|
|
7
|
+
* Reads a phase->gates mapping from the `ids.gateEvaluation` block of
|
|
8
|
+
* core-config.yaml, instantiates the mapped gates, calls verify(context) on
|
|
9
|
+
* each, and aggregates a single PhaseVerdict.
|
|
10
|
+
*
|
|
11
|
+
* Aggregation contract:
|
|
12
|
+
* - Advisory gates (G1-G4): collect warnings/opportunities, never block.
|
|
13
|
+
* - Blocking gates (G3 soft, G5 hard): if a gate result reports
|
|
14
|
+
* result.blocking === true, the verdict is marked blocked, carrying the
|
|
15
|
+
* blocking gateId and the correctionPrompt extracted from the gate override.
|
|
16
|
+
*
|
|
17
|
+
* Fail-open is enforced at two levels:
|
|
18
|
+
* 1. VerificationGate.verify() never throws (timeout/error -> warn-and-proceed).
|
|
19
|
+
* 2. This evaluator wraps each gate.verify() in try/catch — a single broken
|
|
20
|
+
* gate is logged and skipped; it never knocks out the other gates.
|
|
21
|
+
*
|
|
22
|
+
* Source: IDS Gate Wiring frente, ids-principles.md G1-G6 definitions.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const path = require('path');
|
|
26
|
+
|
|
27
|
+
const { G1EpicCreationGate } = require(path.resolve(__dirname, 'gates/g1-epic-creation.js'));
|
|
28
|
+
const { G2StoryCreationGate } = require(path.resolve(__dirname, 'gates/g2-story-creation.js'));
|
|
29
|
+
const { G3StoryValidationGate } = require(path.resolve(__dirname, 'gates/g3-story-validation.js'));
|
|
30
|
+
const { G4DevContextGate } = require(path.resolve(__dirname, 'gates/g4-dev-context.js'));
|
|
31
|
+
const { G5SemanticHandshakeGate } = require(path.resolve(__dirname, 'gates/g5-semantic-handshake.js'));
|
|
32
|
+
const { G6CiIntegrityGate } = require(path.resolve(__dirname, 'gates/g6-ci-integrity.js'));
|
|
33
|
+
const { RegistryLoader } = require(path.resolve(__dirname, 'registry-loader.js'));
|
|
34
|
+
const { IncrementalDecisionEngine } = require(path.resolve(__dirname, 'incremental-decision-engine.js'));
|
|
35
|
+
|
|
36
|
+
const DEFAULT_TIMEOUT_MS = 2000;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Default phase->gates mapping used when the config omits `phaseGates`.
|
|
40
|
+
* Mirrors the SDC trigger points from ids-principles.md.
|
|
41
|
+
*/
|
|
42
|
+
const DEFAULT_PHASE_GATES = Object.freeze({
|
|
43
|
+
epic_creation: ['G1'],
|
|
44
|
+
story_creation: ['G2'],
|
|
45
|
+
story_validation: ['G3'],
|
|
46
|
+
'2_development': ['G4', 'G5'],
|
|
47
|
+
ci_cd: ['G6'],
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {object} PhaseVerdict
|
|
52
|
+
* @property {string} phase - Phase id evaluated (e.g. '2_development').
|
|
53
|
+
* @property {Array<object>} results - GateResult of each gate executed, in order.
|
|
54
|
+
* @property {boolean} blocked - true only when a blocking gate failed.
|
|
55
|
+
* @property {string|null} blockingGate - gateId of the gate that blocked, or null.
|
|
56
|
+
* @property {string|null} correctionPrompt - correction prompt of the blocking gate, or null.
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
class GateEvaluator {
|
|
60
|
+
/**
|
|
61
|
+
* @param {object} [config] — the `ids` block of core-config (expects config.gateEvaluation).
|
|
62
|
+
* @param {object} [deps] — dependency injection seam for tests.
|
|
63
|
+
* @param {RegistryLoader} [deps.registryLoader]
|
|
64
|
+
* @param {IncrementalDecisionEngine} [deps.decisionEngine]
|
|
65
|
+
* @param {object} [deps.engine] — pre-configured SemanticHandshakeEngine for G5.
|
|
66
|
+
* @param {object} [deps.logger] — logger with warn/info/error (defaults to console).
|
|
67
|
+
*/
|
|
68
|
+
constructor(config = {}, deps = {}) {
|
|
69
|
+
const gateConfig = config.gateEvaluation || {};
|
|
70
|
+
|
|
71
|
+
this._enabled = gateConfig.enabled !== false; // default enabled
|
|
72
|
+
this._failOpen = gateConfig.failOpen !== false; // default fail-open
|
|
73
|
+
this._phaseGates = gateConfig.phaseGates || DEFAULT_PHASE_GATES;
|
|
74
|
+
this._timeoutMs = gateConfig.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
75
|
+
this._registryPath = gateConfig.registryPath || null;
|
|
76
|
+
this._logger = deps.logger || console;
|
|
77
|
+
|
|
78
|
+
// Shared, lazily-built dependencies (mirrors FrameworkGovernor wiring).
|
|
79
|
+
this._injectedRegistryLoader = deps.registryLoader || null;
|
|
80
|
+
this._injectedDecisionEngine = deps.decisionEngine || null;
|
|
81
|
+
this._injectedEngine = deps.engine || null;
|
|
82
|
+
|
|
83
|
+
this._registryLoader = this._injectedRegistryLoader;
|
|
84
|
+
this._decisionEngine = this._injectedDecisionEngine;
|
|
85
|
+
|
|
86
|
+
// Cache of instantiated gates by gateId.
|
|
87
|
+
this._gateCache = new Map();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ================================================================
|
|
91
|
+
// Public API
|
|
92
|
+
// ================================================================
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Evaluate every gate mapped to `phaseId` and return the aggregated verdict.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} phaseId — Phase identifier (e.g. '2_development').
|
|
98
|
+
* @param {object} [context] — Gate-specific context (intent, storyId, proposedCode, ...).
|
|
99
|
+
* @returns {Promise<PhaseVerdict>}
|
|
100
|
+
*/
|
|
101
|
+
async evaluateGatesForPhase(phaseId, context = {}) {
|
|
102
|
+
const verdict = {
|
|
103
|
+
phase: phaseId,
|
|
104
|
+
results: [],
|
|
105
|
+
blocked: false,
|
|
106
|
+
blockingGate: null,
|
|
107
|
+
correctionPrompt: null,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
if (!this._enabled) {
|
|
111
|
+
return verdict;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const gateIds = this._phaseGates[phaseId] || [];
|
|
115
|
+
|
|
116
|
+
for (const gateId of gateIds) {
|
|
117
|
+
const result = await this._runGate(gateId, context);
|
|
118
|
+
if (!result) {
|
|
119
|
+
// Gate could not be built/run — already logged. Skip, never block.
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
verdict.results.push(result);
|
|
124
|
+
|
|
125
|
+
// B.2 — Emit IDS telemetry gate decision (fail-open, never throws).
|
|
126
|
+
try {
|
|
127
|
+
const { emitGateDecision } = require('../telemetry/ids-sink');
|
|
128
|
+
emitGateDecision({
|
|
129
|
+
tool: gateId,
|
|
130
|
+
phase: phaseId,
|
|
131
|
+
blocked: !!(result.result && result.result.blocking === true),
|
|
132
|
+
warnings: (result.result && result.result.warnings) || [],
|
|
133
|
+
});
|
|
134
|
+
} catch { /* fail-open: telemetry must never block gate evaluation */ }
|
|
135
|
+
|
|
136
|
+
// A blocking gate that failed marks the verdict blocked.
|
|
137
|
+
if (result.result && result.result.blocking === true) {
|
|
138
|
+
verdict.blocked = true;
|
|
139
|
+
verdict.blockingGate = gateId;
|
|
140
|
+
verdict.correctionPrompt = this._extractCorrectionPrompt(result);
|
|
141
|
+
// First blocking gate wins; stop evaluating further gates for this phase.
|
|
142
|
+
return verdict;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return verdict;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get the gate ids mapped to a phase (empty array if none).
|
|
151
|
+
* @param {string} phaseId
|
|
152
|
+
* @returns {string[]}
|
|
153
|
+
*/
|
|
154
|
+
getGatesForPhase(phaseId) {
|
|
155
|
+
return [...(this._phaseGates[phaseId] || [])];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ================================================================
|
|
159
|
+
// Internal helpers
|
|
160
|
+
// ================================================================
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Run a single gate, guarding against construction/verification errors.
|
|
164
|
+
* @param {string} gateId
|
|
165
|
+
* @param {object} context
|
|
166
|
+
* @returns {Promise<object|null>} GateResult or null when the gate was skipped.
|
|
167
|
+
*/
|
|
168
|
+
async _runGate(gateId, context) {
|
|
169
|
+
let gate;
|
|
170
|
+
try {
|
|
171
|
+
gate = this._getGate(gateId);
|
|
172
|
+
} catch (buildError) {
|
|
173
|
+
this._log('warn', `Gate ${gateId} could not be instantiated: ${buildError.message}`);
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!gate) {
|
|
178
|
+
this._log('warn', `Gate ${gateId} is not registered. Skipping.`);
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
// VerificationGate.verify() is itself fail-open, but we still guard here
|
|
184
|
+
// so a broken individual gate never derails the rest of the phase.
|
|
185
|
+
return await gate.verify(context);
|
|
186
|
+
} catch (verifyError) {
|
|
187
|
+
this._log('warn', `Gate ${gateId} verify() threw: ${verifyError.message}. Proceeding.`);
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Lazily instantiate (and cache) the gate for a gateId.
|
|
194
|
+
* @param {string} gateId
|
|
195
|
+
* @returns {object|null}
|
|
196
|
+
*/
|
|
197
|
+
_getGate(gateId) {
|
|
198
|
+
if (this._gateCache.has(gateId)) {
|
|
199
|
+
return this._gateCache.get(gateId);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const gate = this._buildGate(gateId);
|
|
203
|
+
if (gate) {
|
|
204
|
+
this._gateCache.set(gateId, gate);
|
|
205
|
+
}
|
|
206
|
+
return gate;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Construct the gate instance for a gateId, wiring shared dependencies.
|
|
211
|
+
* @param {string} gateId
|
|
212
|
+
* @returns {object|null}
|
|
213
|
+
*/
|
|
214
|
+
_buildGate(gateId) {
|
|
215
|
+
const logger = this._logger;
|
|
216
|
+
const timeoutMs = this._timeoutMs;
|
|
217
|
+
|
|
218
|
+
switch (gateId) {
|
|
219
|
+
case 'G1':
|
|
220
|
+
return new G1EpicCreationGate({
|
|
221
|
+
decisionEngine: this._getDecisionEngine(),
|
|
222
|
+
timeoutMs,
|
|
223
|
+
logger,
|
|
224
|
+
});
|
|
225
|
+
case 'G2':
|
|
226
|
+
return new G2StoryCreationGate({
|
|
227
|
+
decisionEngine: this._getDecisionEngine(),
|
|
228
|
+
timeoutMs,
|
|
229
|
+
logger,
|
|
230
|
+
});
|
|
231
|
+
case 'G3':
|
|
232
|
+
return new G3StoryValidationGate({
|
|
233
|
+
decisionEngine: this._getDecisionEngine(),
|
|
234
|
+
registryLoader: this._getRegistryLoader(),
|
|
235
|
+
timeoutMs,
|
|
236
|
+
logger,
|
|
237
|
+
});
|
|
238
|
+
case 'G4':
|
|
239
|
+
return new G4DevContextGate({
|
|
240
|
+
decisionEngine: this._getDecisionEngine(),
|
|
241
|
+
timeoutMs,
|
|
242
|
+
logger,
|
|
243
|
+
});
|
|
244
|
+
case 'G5':
|
|
245
|
+
return new G5SemanticHandshakeGate({
|
|
246
|
+
engine: this._injectedEngine || undefined,
|
|
247
|
+
timeoutMs,
|
|
248
|
+
logger,
|
|
249
|
+
});
|
|
250
|
+
case 'G6':
|
|
251
|
+
return new G6CiIntegrityGate({
|
|
252
|
+
registryLoader: this._getRegistryLoader(),
|
|
253
|
+
registryPath: this._registryPath || undefined,
|
|
254
|
+
logger,
|
|
255
|
+
});
|
|
256
|
+
default:
|
|
257
|
+
this._log('warn', `Unknown gateId '${gateId}' in phaseGates mapping.`);
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Lazily build (and memoize) the shared RegistryLoader.
|
|
264
|
+
* @returns {RegistryLoader}
|
|
265
|
+
*/
|
|
266
|
+
_getRegistryLoader() {
|
|
267
|
+
if (!this._registryLoader) {
|
|
268
|
+
this._registryLoader = new RegistryLoader(this._registryPath || undefined);
|
|
269
|
+
}
|
|
270
|
+
return this._registryLoader;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Lazily build (and memoize) the shared IncrementalDecisionEngine.
|
|
275
|
+
* @returns {IncrementalDecisionEngine}
|
|
276
|
+
*/
|
|
277
|
+
_getDecisionEngine() {
|
|
278
|
+
if (!this._decisionEngine) {
|
|
279
|
+
this._decisionEngine = new IncrementalDecisionEngine(this._getRegistryLoader());
|
|
280
|
+
}
|
|
281
|
+
return this._decisionEngine;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Extract the correction prompt from a blocking gate result.
|
|
286
|
+
* Falls back to a generic message when the gate did not provide one.
|
|
287
|
+
* @param {object} result — GateResult.
|
|
288
|
+
* @returns {string}
|
|
289
|
+
*/
|
|
290
|
+
_extractCorrectionPrompt(result) {
|
|
291
|
+
const override = result && result.override;
|
|
292
|
+
if (override && override.correctionPrompt) {
|
|
293
|
+
return override.correctionPrompt;
|
|
294
|
+
}
|
|
295
|
+
const warnings = (result && result.result && result.result.warnings) || [];
|
|
296
|
+
if (warnings.length > 0) {
|
|
297
|
+
return warnings.join('; ');
|
|
298
|
+
}
|
|
299
|
+
return `Gate ${result && result.gateId} reported a blocking violation.`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Internal logging helper.
|
|
304
|
+
* @param {string} level
|
|
305
|
+
* @param {string} message
|
|
306
|
+
*/
|
|
307
|
+
_log(level, message) {
|
|
308
|
+
const prefix = '[IDS-GateEvaluator]';
|
|
309
|
+
const logFn = (this._logger && this._logger[level]) || (this._logger && this._logger.log) || console.log;
|
|
310
|
+
logFn(`${prefix} ${message}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
module.exports = {
|
|
315
|
+
GateEvaluator,
|
|
316
|
+
DEFAULT_PHASE_GATES,
|
|
317
|
+
DEFAULT_TIMEOUT_MS,
|
|
318
|
+
};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* G5 — Semantic Handshake Gate
|
|
5
|
+
*
|
|
6
|
+
* Agent: @developer
|
|
7
|
+
* Type: Automated, Blocking
|
|
8
|
+
* Latency SLA: < 2s
|
|
9
|
+
* Blocking: YES (blocks on BLOCKER-severity constraint violations)
|
|
10
|
+
*
|
|
11
|
+
* Purpose: Converts planning constraints (from @architect / story / planning
|
|
12
|
+
* output) into executable assertions and validates proposed code against them
|
|
13
|
+
* BEFORE development execution proceeds. If a registered BLOCKER constraint is
|
|
14
|
+
* violated by the proposed code, the gate fails and returns a correction prompt.
|
|
15
|
+
*
|
|
16
|
+
* Behavior is graceful-degradation friendly (inherited from VerificationGate):
|
|
17
|
+
* - No constraints registered → passes (advisory note).
|
|
18
|
+
* - Constraints but no code → passes, surfaces constraints as opportunities.
|
|
19
|
+
* - Code + constraints → validates; BLOCKER violation → passed=false.
|
|
20
|
+
*
|
|
21
|
+
* Composes with SemanticHandshakeEngine (public API):
|
|
22
|
+
* getConstraints, registerConstraints, addConstraint, clone,
|
|
23
|
+
* validateExecutionIntent, generateComplianceReport.
|
|
24
|
+
*
|
|
25
|
+
* Source: IDS-5b (Blocking Gates), ids-principles.md G5 definition.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const path = require('path');
|
|
29
|
+
const { VerificationGate } = require(path.resolve(__dirname, '../verification-gate.js'));
|
|
30
|
+
const { SemanticHandshakeEngine } = require(
|
|
31
|
+
path.resolve(__dirname, '../../synapse/context/semantic-handshake-engine.js'),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const G5_DEFAULT_TIMEOUT_MS = 2000;
|
|
35
|
+
|
|
36
|
+
class G5SemanticHandshakeGate extends VerificationGate {
|
|
37
|
+
/**
|
|
38
|
+
* @param {object} options
|
|
39
|
+
* @param {SemanticHandshakeEngine} [options.engine] — Pre-configured engine instance
|
|
40
|
+
* @param {object[]} [options.constraints] — Seed constraints (used when no engine provided)
|
|
41
|
+
* @param {object} [options.circuitBreakerOptions]
|
|
42
|
+
* @param {number} [options.timeoutMs=2000]
|
|
43
|
+
* @param {Function} [options.logger]
|
|
44
|
+
*/
|
|
45
|
+
constructor(options = {}) {
|
|
46
|
+
super({
|
|
47
|
+
gateId: 'G5',
|
|
48
|
+
agent: '@developer',
|
|
49
|
+
blocking: true,
|
|
50
|
+
timeoutMs: options.timeoutMs ?? G5_DEFAULT_TIMEOUT_MS,
|
|
51
|
+
circuitBreakerOptions: options.circuitBreakerOptions,
|
|
52
|
+
logger: options.logger,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
this._engine = options.engine || new SemanticHandshakeEngine({
|
|
56
|
+
constraints: options.constraints || [],
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Validate proposed code against registered + context-derived constraints.
|
|
62
|
+
*
|
|
63
|
+
* @param {object} context
|
|
64
|
+
* @param {string} [context.planningOutput] — Planning / architecture text to extract constraints from
|
|
65
|
+
* @param {string} [context.planningText]
|
|
66
|
+
* @param {string} [context.architectureText]
|
|
67
|
+
* @param {string} [context.storyText]
|
|
68
|
+
* @param {object[]} [context.constraints] — Explicit structured constraints to add
|
|
69
|
+
* @param {string} [context.proposedCode] — Proposed code to validate
|
|
70
|
+
* @param {Array} [context.files] — File list ({ path, content }) to validate
|
|
71
|
+
* @param {Array} [context.diffs]
|
|
72
|
+
* @param {Array} [context.codeFiles]
|
|
73
|
+
* @param {string} [context.source] — Constraint source label (default '@architect')
|
|
74
|
+
* @param {string} [context.storyId]
|
|
75
|
+
* @returns {Promise<object>} { passed, warnings, opportunities, override }
|
|
76
|
+
*/
|
|
77
|
+
async _doVerify(context = {}) {
|
|
78
|
+
const engine = this._createScopedEngine(context);
|
|
79
|
+
const constraints = engine.getConstraints();
|
|
80
|
+
|
|
81
|
+
if (constraints.length === 0) {
|
|
82
|
+
return {
|
|
83
|
+
passed: true,
|
|
84
|
+
warnings: ['No Semantic Handshake constraints registered'],
|
|
85
|
+
opportunities: [],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!this._hasCodeContext(context)) {
|
|
90
|
+
return {
|
|
91
|
+
passed: true,
|
|
92
|
+
warnings: ['Semantic Handshake constraints registered, but no proposed code was provided'],
|
|
93
|
+
opportunities: constraints.map((c) => ({
|
|
94
|
+
entity: c.id,
|
|
95
|
+
recommendation: c.description,
|
|
96
|
+
severity: c.severity,
|
|
97
|
+
})),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const result = await engine.validateExecutionIntent(context);
|
|
102
|
+
const report = engine.generateComplianceReport(result);
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
passed: result.passed,
|
|
106
|
+
warnings: [
|
|
107
|
+
...result.warnings,
|
|
108
|
+
...result.blockingViolations.map((v) => v.message),
|
|
109
|
+
],
|
|
110
|
+
opportunities: [
|
|
111
|
+
...result.verifiedConstraints.map((c) => ({
|
|
112
|
+
entity: c.id,
|
|
113
|
+
recommendation: 'Constraint verified',
|
|
114
|
+
severity: c.severity,
|
|
115
|
+
})),
|
|
116
|
+
...result.violations.map((v) => ({
|
|
117
|
+
entity: v.id,
|
|
118
|
+
recommendation: v.message,
|
|
119
|
+
severity: v.severity,
|
|
120
|
+
matches: v.matches,
|
|
121
|
+
})),
|
|
122
|
+
],
|
|
123
|
+
override: result.passed
|
|
124
|
+
? null
|
|
125
|
+
: {
|
|
126
|
+
reason: 'Semantic Handshake blocker violation',
|
|
127
|
+
correctionPrompt: result.correctionPrompt,
|
|
128
|
+
report,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Build a per-invocation engine scoped to this context's constraints so the
|
|
135
|
+
* gate instance stays stateless across calls.
|
|
136
|
+
* @param {object} context
|
|
137
|
+
* @returns {SemanticHandshakeEngine}
|
|
138
|
+
*/
|
|
139
|
+
_createScopedEngine(context) {
|
|
140
|
+
const engine = typeof this._engine.clone === 'function'
|
|
141
|
+
? this._engine.clone()
|
|
142
|
+
: new SemanticHandshakeEngine();
|
|
143
|
+
this._registerContextConstraints(engine, context);
|
|
144
|
+
return engine;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Register constraints derived from planning text and explicit constraint
|
|
149
|
+
* objects supplied in the context.
|
|
150
|
+
* @param {SemanticHandshakeEngine} engine
|
|
151
|
+
* @param {object} context
|
|
152
|
+
*/
|
|
153
|
+
_registerContextConstraints(engine, context) {
|
|
154
|
+
const planningText = [
|
|
155
|
+
context.planningOutput,
|
|
156
|
+
context.planningText,
|
|
157
|
+
context.architectureText,
|
|
158
|
+
context.storyText,
|
|
159
|
+
].filter(Boolean).join('\n\n');
|
|
160
|
+
|
|
161
|
+
if (planningText.trim()) {
|
|
162
|
+
engine.registerConstraints(planningText, {
|
|
163
|
+
source: context.source || '@architect',
|
|
164
|
+
metadata: { storyId: context.storyId || 'unknown' },
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (Array.isArray(context.constraints)) {
|
|
169
|
+
for (const c of context.constraints) {
|
|
170
|
+
engine.addConstraint(c);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Determine whether the context carries any code to validate.
|
|
177
|
+
* @param {object} context
|
|
178
|
+
* @returns {boolean}
|
|
179
|
+
*/
|
|
180
|
+
_hasCodeContext(context) {
|
|
181
|
+
return Boolean(
|
|
182
|
+
context.proposedCode ||
|
|
183
|
+
(Array.isArray(context.files) && context.files.length > 0) ||
|
|
184
|
+
(Array.isArray(context.diffs) && context.diffs.length > 0) ||
|
|
185
|
+
(Array.isArray(context.codeFiles) && context.codeFiles.length > 0),
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = { G5SemanticHandshakeGate, G5_DEFAULT_TIMEOUT_MS };
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* G6 — CI/CD Registry Integrity Gate
|
|
5
|
+
*
|
|
6
|
+
* Agent: @devops
|
|
7
|
+
* Type: Automated, Blocking
|
|
8
|
+
* Latency SLA: < 60s (registry integrity check + sync)
|
|
9
|
+
* Blocking: YES on CRITICAL, WARN on MEDIUM/LOW
|
|
10
|
+
*
|
|
11
|
+
* Purpose: The CI/CD merge gate of the IDS pipeline. Before a push/merge is
|
|
12
|
+
* accepted it (1) verifies the Entity Registry is structurally intact and
|
|
13
|
+
* (2) syncs the registry against the set of changed files — the same work the
|
|
14
|
+
* `.sinapse-ai/hooks/ids-pre-push.js` hook performs, but exposed as a first-class
|
|
15
|
+
* gate so the GateEvaluator can wire G6 into the SDC pipeline (ids-principles.md
|
|
16
|
+
* documented G6 while the code only shipped G1-G5).
|
|
17
|
+
*
|
|
18
|
+
* Severity model (ids-principles.md G6):
|
|
19
|
+
* - CRITICAL → registry fails to load / is corrupt / lost its `entities` root
|
|
20
|
+
* → passed=false → blocks merge, carries a correction prompt.
|
|
21
|
+
* - MEDIUM/LOW → per-file sync errors → surfaced as warnings, never block.
|
|
22
|
+
*
|
|
23
|
+
* Graceful-degradation friendly (inherited from VerificationGate): a thrown
|
|
24
|
+
* error inside _doVerify is caught by the base class and converted to a
|
|
25
|
+
* warn-and-proceed, so a flaky CI environment never hard-blocks a merge by
|
|
26
|
+
* accident — only a genuine integrity violation does.
|
|
27
|
+
*
|
|
28
|
+
* Source: IDS-5b (Blocking Gates), ids-principles.md G6 definition.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
const path = require('path');
|
|
32
|
+
const { VerificationGate } = require(path.resolve(__dirname, '../verification-gate.js'));
|
|
33
|
+
const { RegistryLoader } = require(path.resolve(__dirname, '../registry-loader.js'));
|
|
34
|
+
|
|
35
|
+
// G6 runs in CI; its SLA is generous (< 60s) vs the in-editor gates (< 2s).
|
|
36
|
+
const G6_DEFAULT_TIMEOUT_MS = 60000;
|
|
37
|
+
|
|
38
|
+
class G6CiIntegrityGate extends VerificationGate {
|
|
39
|
+
/**
|
|
40
|
+
* @param {object} options
|
|
41
|
+
* @param {RegistryLoader} [options.registryLoader] — Injected loader (tests).
|
|
42
|
+
* @param {object} [options.registryUpdater] — Injected updater (tests);
|
|
43
|
+
* defaults to a lazily-required RegistryUpdater instance.
|
|
44
|
+
* @param {string} [options.registryPath] — Registry path override.
|
|
45
|
+
* @param {object} [options.circuitBreakerOptions]
|
|
46
|
+
* @param {number} [options.timeoutMs=60000]
|
|
47
|
+
* @param {Function} [options.logger]
|
|
48
|
+
*/
|
|
49
|
+
constructor(options = {}) {
|
|
50
|
+
super({
|
|
51
|
+
gateId: 'G6',
|
|
52
|
+
agent: '@devops',
|
|
53
|
+
blocking: true,
|
|
54
|
+
timeoutMs: options.timeoutMs ?? G6_DEFAULT_TIMEOUT_MS,
|
|
55
|
+
circuitBreakerOptions: options.circuitBreakerOptions,
|
|
56
|
+
logger: options.logger,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
this._registryPath = options.registryPath || null;
|
|
60
|
+
this._injectedLoader = options.registryLoader || null;
|
|
61
|
+
this._injectedUpdater = options.registryUpdater || null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Verify registry integrity and sync against changed files.
|
|
66
|
+
*
|
|
67
|
+
* @param {object} context
|
|
68
|
+
* @param {Array<object>} [context.changes] — Changed files since remote, each
|
|
69
|
+
* `{ action: 'add'|'change'|'unlink', relativePath, filePath }`
|
|
70
|
+
* (the shape produced by ids-pre-push.js). Optional — when absent, only
|
|
71
|
+
* the integrity check runs.
|
|
72
|
+
* @returns {Promise<object>} { passed, warnings, opportunities, override }
|
|
73
|
+
*/
|
|
74
|
+
async _doVerify(context = {}) {
|
|
75
|
+
const warnings = [];
|
|
76
|
+
|
|
77
|
+
// ── 1. Integrity check (CRITICAL) ──────────────────────────────────────
|
|
78
|
+
let entityCount = 0;
|
|
79
|
+
try {
|
|
80
|
+
const loader = this._getLoader();
|
|
81
|
+
const registry = loader.load();
|
|
82
|
+
if (!registry || !registry.entities) {
|
|
83
|
+
return this._block('Entity Registry loaded but has no `entities` root.');
|
|
84
|
+
}
|
|
85
|
+
entityCount = Object.keys(registry.entities).length;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
return this._block(`Entity Registry failed to load (corrupt or unreadable): ${error.message}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── 2. Registry sync against changed files (MEDIUM/LOW → warnings) ──────
|
|
91
|
+
const changes = Array.isArray(context.changes) ? context.changes : [];
|
|
92
|
+
let synced = 0;
|
|
93
|
+
if (changes.length > 0) {
|
|
94
|
+
try {
|
|
95
|
+
const updater = this._getUpdater();
|
|
96
|
+
const result = await updater.processChanges(changes);
|
|
97
|
+
synced = (result && result.updated) || 0;
|
|
98
|
+
const errors = (result && result.errors) || [];
|
|
99
|
+
for (const err of errors) {
|
|
100
|
+
// Sync errors are MEDIUM/LOW: surface them, but do not block the merge.
|
|
101
|
+
warnings.push(`[G6] Registry sync issue (non-blocking): ${err.message || err}`);
|
|
102
|
+
}
|
|
103
|
+
} catch (error) {
|
|
104
|
+
// A sync failure is non-critical — the integrity check already passed.
|
|
105
|
+
warnings.push(`[G6] Registry sync failed (non-blocking): ${error.message}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
passed: true,
|
|
111
|
+
warnings,
|
|
112
|
+
opportunities: [],
|
|
113
|
+
meta: { entityCount, synced },
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Build a blocking verdict carrying a correction prompt.
|
|
119
|
+
* @param {string} reason
|
|
120
|
+
* @returns {object}
|
|
121
|
+
* @private
|
|
122
|
+
*/
|
|
123
|
+
_block(reason) {
|
|
124
|
+
const correctionPrompt =
|
|
125
|
+
`${reason}\n\nFix the Entity Registry before merging: run \`sinapse ids:health\` to ` +
|
|
126
|
+
'diagnose, restore the registry from a known-good state, and re-run the registry sync.';
|
|
127
|
+
return {
|
|
128
|
+
passed: false,
|
|
129
|
+
warnings: [`[G6] CRITICAL: ${reason}`],
|
|
130
|
+
opportunities: [],
|
|
131
|
+
override: { correctionPrompt },
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Lazily build (and memoize) the RegistryLoader.
|
|
137
|
+
* @returns {RegistryLoader}
|
|
138
|
+
* @private
|
|
139
|
+
*/
|
|
140
|
+
_getLoader() {
|
|
141
|
+
if (!this._injectedLoader) {
|
|
142
|
+
this._injectedLoader = new RegistryLoader(this._registryPath || undefined);
|
|
143
|
+
}
|
|
144
|
+
return this._injectedLoader;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Lazily build (and memoize) the RegistryUpdater. Required only when there are
|
|
149
|
+
* changes to sync, so the dependency stays out of the integrity-only path.
|
|
150
|
+
* @returns {object}
|
|
151
|
+
* @private
|
|
152
|
+
*/
|
|
153
|
+
_getUpdater() {
|
|
154
|
+
if (!this._injectedUpdater) {
|
|
155
|
+
const { RegistryUpdater } = require(path.resolve(__dirname, '../registry-updater.js'));
|
|
156
|
+
this._injectedUpdater = new RegistryUpdater();
|
|
157
|
+
}
|
|
158
|
+
return this._injectedUpdater;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = { G6CiIntegrityGate, G6_DEFAULT_TIMEOUT_MS };
|