auroq-os 1.0.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/.auroq-core/constitution.md +153 -0
- package/.auroq-core/core/synapse/context/context-builder.js +34 -0
- package/.auroq-core/core/synapse/context/context-tracker.js +198 -0
- package/.auroq-core/core/synapse/diagnostics/collectors/consistency-collector.js +168 -0
- package/.auroq-core/core/synapse/diagnostics/collectors/hook-collector.js +129 -0
- package/.auroq-core/core/synapse/diagnostics/collectors/manifest-collector.js +82 -0
- package/.auroq-core/core/synapse/diagnostics/collectors/output-analyzer.js +134 -0
- package/.auroq-core/core/synapse/diagnostics/collectors/pipeline-collector.js +75 -0
- package/.auroq-core/core/synapse/diagnostics/collectors/quality-collector.js +252 -0
- package/.auroq-core/core/synapse/diagnostics/collectors/relevance-matrix.js +174 -0
- package/.auroq-core/core/synapse/diagnostics/collectors/safe-read-json.js +31 -0
- package/.auroq-core/core/synapse/diagnostics/collectors/session-collector.js +102 -0
- package/.auroq-core/core/synapse/diagnostics/collectors/timing-collector.js +126 -0
- package/.auroq-core/core/synapse/diagnostics/collectors/uap-collector.js +83 -0
- package/.auroq-core/core/synapse/diagnostics/report-formatter.js +484 -0
- package/.auroq-core/core/synapse/diagnostics/synapse-diagnostics.js +95 -0
- package/.auroq-core/core/synapse/domain/domain-loader.js +322 -0
- package/.auroq-core/core/synapse/engine.js +400 -0
- package/.auroq-core/core/synapse/layers/l0-constitution.js +80 -0
- package/.auroq-core/core/synapse/layers/l1-global.js +102 -0
- package/.auroq-core/core/synapse/layers/l2-agent.js +94 -0
- package/.auroq-core/core/synapse/layers/l3-workflow.js +94 -0
- package/.auroq-core/core/synapse/layers/l4-task.js +83 -0
- package/.auroq-core/core/synapse/layers/l5-squad.js +244 -0
- package/.auroq-core/core/synapse/layers/l6-keyword.js +154 -0
- package/.auroq-core/core/synapse/layers/l7-star-command.js +169 -0
- package/.auroq-core/core/synapse/layers/layer-processor.js +82 -0
- package/.auroq-core/core/synapse/memory/memory-bridge.js +220 -0
- package/.auroq-core/core/synapse/memory/synapse-memory-provider.js +201 -0
- package/.auroq-core/core/synapse/output/formatter.js +561 -0
- package/.auroq-core/core/synapse/runtime/hook-runtime.js +98 -0
- package/.auroq-core/core/synapse/scripts/generate-constitution.js +204 -0
- package/.auroq-core/core/synapse/session/session-manager.js +404 -0
- package/.auroq-core/core/synapse/utils/atomic-write.js +79 -0
- package/.auroq-core/core/synapse/utils/paths.js +57 -0
- package/.auroq-core/core/synapse/utils/tokens.js +25 -0
- package/.auroq-core/core-config.yaml +80 -0
- package/.auroq-core/development/sistema-gestao-projetos.md +193 -0
- package/.auroq-core/development/sistema-memoria.md +157 -0
- package/.auroq-core/dna-operacional.md +134 -0
- package/.claude/CLAUDE.md +399 -0
- package/.claude/commands/AuroqOS/agents/forge.md +374 -0
- package/.claude/commands/AuroqOS/agents/ops.md +277 -0
- package/.claude/commands/companion.md +7 -0
- package/.claude/hooks/precompact-session-digest.cjs +46 -0
- package/.claude/hooks/synapse-engine.cjs +99 -0
- package/.claude/rules/agent-authority.md +79 -0
- package/.claude/rules/agent-handoff.md +97 -0
- package/.claude/rules/commit-inteligente.md +86 -0
- package/.claude/rules/dna-operacional.md +53 -0
- package/.claude/rules/evolucao-incremental.md +43 -0
- package/.claude/rules/mcp-usage.md +38 -0
- package/.claude/rules/memoria-inteligente.md +65 -0
- package/.claude/rules/project-tracker.md +49 -0
- package/.claude/rules/tool-response-filtering.md +57 -0
- package/.claude/settings.json +1 -0
- package/.claude/settings.local.json +33 -0
- package/.env.example +22 -0
- package/.gitignore +23 -0
- package/.synapse/constitution +21 -0
- package/.synapse/context +8 -0
- package/.synapse/global +10 -0
- package/.synapse/manifest +35 -0
- package/README.md +67 -0
- package/agents/companion/agents/companion.md +306 -0
- package/agents/companion/data/contexto-dinamico.md +23 -0
- package/agents/companion/data/demandas-backlog.md +19 -0
- package/agents/companion/data/log-decisoes.md +11 -0
- package/agents/companion/data/padroes-observados.md +10 -0
- package/agents/companion/knowledge/companion-foundation.md +49 -0
- package/agents/companion/knowledge/expert-essencial.md +30 -0
- package/agents/companion/knowledge/modus-operandi.md +161 -0
- package/agents/companion/tasks/new-project.md +111 -0
- package/agents/companion/tasks/save-memory.md +73 -0
- package/agents/companion/tasks/start.md +136 -0
- package/agents/companion/tasks/weekly-review.md +82 -0
- package/bin/auroq-os.js +346 -0
- package/business/campanhas/_template/BRIEFING.md +45 -0
- package/business/campanhas/_template/CHECKLIST.md +29 -0
- package/business/campanhas/_template/DIARIO.md +15 -0
- package/business/campanhas/_template/RETROVISOR.md +34 -0
- package/business/cockpit.md +72 -0
- package/business/templates/tracker-template.md +71 -0
- package/docs/knowledge/expert-business/posicionamento.md +27 -0
- package/docs/knowledge/expert-business/publico-alvo.md +39 -0
- package/docs/knowledge/expert-mind/historia.md +27 -0
- package/docs/knowledge/expert-mind/identidade.md +23 -0
- package/docs/knowledge/expert-mind/tom-de-voz.md +30 -0
- package/docs/knowledge/expert-mind/valores.md +32 -0
- package/package.json +26 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SynapseEngine — Orchestrator for the 8-layer context injection pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Executes L0-L7 sequentially, applies bracket-aware filtering via
|
|
5
|
+
* context-tracker (SYN-3), collects pipeline metrics, and formats
|
|
6
|
+
* the final <synapse-rules> XML output via the formatter module.
|
|
7
|
+
*
|
|
8
|
+
* @module core/synapse/engine
|
|
9
|
+
* @version 1.0.0
|
|
10
|
+
* @created Story SYN-6 - SynapseEngine Orchestrator + Output Formatter
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
estimateContextPercent,
|
|
18
|
+
calculateBracket,
|
|
19
|
+
getActiveLayers,
|
|
20
|
+
getTokenBudget,
|
|
21
|
+
needsMemoryHints,
|
|
22
|
+
needsHandoffWarning,
|
|
23
|
+
} = require('./context/context-tracker');
|
|
24
|
+
const { buildLayerContext } = require('./context/context-builder');
|
|
25
|
+
|
|
26
|
+
const { formatSynapseRules } = require('./output/formatter');
|
|
27
|
+
const { MemoryBridge } = require('./memory/memory-bridge');
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Layer Imports (graceful — layers from SYN-4/SYN-5 may not exist yet)
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
const LAYER_MODULES = [
|
|
34
|
+
{ path: './layers/l0-constitution', layer: 0, name: 'constitution' },
|
|
35
|
+
{ path: './layers/l1-global', layer: 1, name: 'global' },
|
|
36
|
+
{ path: './layers/l2-agent', layer: 2, name: 'agent' },
|
|
37
|
+
{ path: './layers/l3-workflow', layer: 3, name: 'workflow' },
|
|
38
|
+
{ path: './layers/l4-task', layer: 4, name: 'task' },
|
|
39
|
+
{ path: './layers/l5-squad', layer: 5, name: 'squad' },
|
|
40
|
+
{ path: './layers/l6-keyword', layer: 6, name: 'keyword' },
|
|
41
|
+
{ path: './layers/l7-star-command', layer: 7, name: 'star-command' },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Safely load a layer module. Returns the class or null if not available.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} modulePath - Relative require path
|
|
48
|
+
* @returns {Function|null} Layer constructor or null
|
|
49
|
+
*/
|
|
50
|
+
function loadLayerModule(modulePath) {
|
|
51
|
+
try {
|
|
52
|
+
return require(modulePath);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
// Only silence MODULE_NOT_FOUND for the requested module
|
|
55
|
+
if (err.code === 'MODULE_NOT_FOUND' && err.message && err.message.includes(modulePath)) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
// Surface unexpected errors (syntax, runtime, transitive missing deps)
|
|
59
|
+
console.warn(`[synapse:engine] Unexpected error loading ${modulePath}: ${err.message}`);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// PipelineMetrics
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Collects timing and statistics for each layer in the pipeline.
|
|
70
|
+
*
|
|
71
|
+
* Used by DEVMODE output and returned in the process() result.
|
|
72
|
+
*/
|
|
73
|
+
class PipelineMetrics {
|
|
74
|
+
constructor() {
|
|
75
|
+
/** @type {Object.<string, object>} Per-layer metrics keyed by name */
|
|
76
|
+
this.layers = {};
|
|
77
|
+
/** @type {bigint|null} Pipeline start hrtime (nanoseconds) */
|
|
78
|
+
this.totalStart = null;
|
|
79
|
+
/** @type {bigint|null} Pipeline end hrtime (nanoseconds) */
|
|
80
|
+
this.totalEnd = null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Mark the start of a layer's execution.
|
|
85
|
+
*
|
|
86
|
+
* @param {string} name - Layer name
|
|
87
|
+
*/
|
|
88
|
+
startLayer(name) {
|
|
89
|
+
this.layers[name] = { start: process.hrtime.bigint(), status: 'running' };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Mark the successful end of a layer's execution.
|
|
94
|
+
*
|
|
95
|
+
* @param {string} name - Layer name
|
|
96
|
+
* @param {number} rulesCount - Number of rules produced
|
|
97
|
+
*/
|
|
98
|
+
endLayer(name, rulesCount) {
|
|
99
|
+
const layer = this.layers[name];
|
|
100
|
+
if (!layer) {
|
|
101
|
+
this.layers[name] = { status: 'ok', rules: rulesCount };
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const endTime = process.hrtime.bigint();
|
|
105
|
+
layer.end = endTime;
|
|
106
|
+
layer.duration = Number(endTime - layer.start) / 1e6;
|
|
107
|
+
layer.status = 'ok';
|
|
108
|
+
layer.rules = rulesCount;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Record that a layer was skipped.
|
|
113
|
+
*
|
|
114
|
+
* @param {string} name - Layer name
|
|
115
|
+
* @param {string} reason - Why it was skipped
|
|
116
|
+
*/
|
|
117
|
+
skipLayer(name, reason) {
|
|
118
|
+
this.layers[name] = { status: 'skipped', reason };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Record that a layer encountered an error.
|
|
123
|
+
*
|
|
124
|
+
* @param {string} name - Layer name
|
|
125
|
+
* @param {Error} error - The error object
|
|
126
|
+
*/
|
|
127
|
+
errorLayer(name, error) {
|
|
128
|
+
const existing = this.layers[name] || {};
|
|
129
|
+
if (existing.start) {
|
|
130
|
+
const endTime = process.hrtime.bigint();
|
|
131
|
+
existing.end = endTime;
|
|
132
|
+
existing.duration = Number(endTime - existing.start) / 1e6;
|
|
133
|
+
}
|
|
134
|
+
this.layers[name] = {
|
|
135
|
+
...existing,
|
|
136
|
+
status: 'error',
|
|
137
|
+
error: error && error.message ? error.message : String(error),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Return a summary of the full pipeline execution.
|
|
143
|
+
*
|
|
144
|
+
* @returns {{
|
|
145
|
+
* total_ms: number,
|
|
146
|
+
* layers_loaded: number,
|
|
147
|
+
* layers_skipped: number,
|
|
148
|
+
* layers_errored: number,
|
|
149
|
+
* total_rules: number,
|
|
150
|
+
* per_layer: Object
|
|
151
|
+
* }}
|
|
152
|
+
*/
|
|
153
|
+
getSummary() {
|
|
154
|
+
const values = Object.values(this.layers);
|
|
155
|
+
return {
|
|
156
|
+
total_ms: this.totalStart != null && this.totalEnd != null
|
|
157
|
+
? Number(this.totalEnd - this.totalStart) / 1e6
|
|
158
|
+
: 0,
|
|
159
|
+
layers_loaded: values.filter(l => l.status === 'ok').length,
|
|
160
|
+
layers_skipped: values.filter(l => l.status === 'skipped').length,
|
|
161
|
+
layers_errored: values.filter(l => l.status === 'error').length,
|
|
162
|
+
total_rules: values.reduce((sum, l) => sum + (l.rules || 0), 0),
|
|
163
|
+
per_layer: this.layers,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
// SynapseEngine
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
/** Hard pipeline timeout in milliseconds. */
|
|
173
|
+
const PIPELINE_TIMEOUT_MS = 100;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* NOG-18: Default active layers (L0-L2 only).
|
|
177
|
+
* L3-L7 produced 0 rules in NOG-17 audit — disabled for performance.
|
|
178
|
+
* Set SYNAPSE_LEGACY_MODE=true to re-enable full 8-layer processing.
|
|
179
|
+
*/
|
|
180
|
+
const DEFAULT_ACTIVE_LAYERS = [0, 1, 2];
|
|
181
|
+
const LEGACY_MODE = process.env.SYNAPSE_LEGACY_MODE === 'true';
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Orchestrates the 8-layer SYNAPSE context injection pipeline.
|
|
185
|
+
*
|
|
186
|
+
* Instantiates all available layers at construction time and
|
|
187
|
+
* executes them sequentially in process(), applying bracket-aware
|
|
188
|
+
* filtering and collecting metrics.
|
|
189
|
+
*/
|
|
190
|
+
class SynapseEngine {
|
|
191
|
+
/**
|
|
192
|
+
* @param {string} synapsePath - Absolute path to the .synapse/ directory
|
|
193
|
+
* @param {object} [config={}] - Configuration from manifest / caller
|
|
194
|
+
* @param {object} [config.manifest] - Parsed manifest object
|
|
195
|
+
* @param {boolean} [config.devmode] - Enable DEVMODE debug output
|
|
196
|
+
*/
|
|
197
|
+
constructor(synapsePath, config = {}) {
|
|
198
|
+
this.synapsePath = synapsePath;
|
|
199
|
+
this.config = config;
|
|
200
|
+
|
|
201
|
+
/** @type {Array<import('./layers/layer-processor')>} */
|
|
202
|
+
this.layers = [];
|
|
203
|
+
|
|
204
|
+
/** @type {MemoryBridge} Feature-gated MIS consumer (SYN-10) */
|
|
205
|
+
this.memoryBridge = new MemoryBridge();
|
|
206
|
+
|
|
207
|
+
for (const mod of LAYER_MODULES) {
|
|
208
|
+
const LayerClass = loadLayerModule(mod.path);
|
|
209
|
+
if (LayerClass) {
|
|
210
|
+
try {
|
|
211
|
+
this.layers.push(new LayerClass());
|
|
212
|
+
} catch (err) {
|
|
213
|
+
console.warn(`[synapse:engine] Failed to instantiate layer ${mod.name}: ${err.message}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Execute the full pipeline for a user prompt.
|
|
221
|
+
*
|
|
222
|
+
* 1. Calculate context bracket via SYN-3 context-tracker
|
|
223
|
+
* 2. Filter active layers for the bracket
|
|
224
|
+
* 3. Execute layers sequentially, accumulating previousLayers
|
|
225
|
+
* 4. Apply memory hint / handoff warning placeholders (SYN-10 future)
|
|
226
|
+
* 5. Format output via formatter module
|
|
227
|
+
*
|
|
228
|
+
* @param {string} prompt - The user prompt text
|
|
229
|
+
* @param {object} session - Session state (SYN-2 schema)
|
|
230
|
+
* @param {number} [session.prompt_count=0] - Number of prompts so far
|
|
231
|
+
* @param {object} [processConfig] - Per-call config overrides
|
|
232
|
+
* @returns {Promise<{ xml: string, metrics: object }>}
|
|
233
|
+
*/
|
|
234
|
+
async process(prompt, session, processConfig) {
|
|
235
|
+
const safeProcessConfig = (processConfig && typeof processConfig === 'object') ? processConfig : {};
|
|
236
|
+
const mergedConfig = { ...this.config, ...safeProcessConfig };
|
|
237
|
+
const metrics = new PipelineMetrics();
|
|
238
|
+
metrics.totalStart = process.hrtime.bigint();
|
|
239
|
+
|
|
240
|
+
// 1. Calculate bracket (or use fixed layers in non-legacy mode)
|
|
241
|
+
const promptCount = (session && session.prompt_count) || 0;
|
|
242
|
+
let contextPercent, bracket, activeLayers, tokenBudget;
|
|
243
|
+
|
|
244
|
+
if (LEGACY_MODE) {
|
|
245
|
+
// Full 8-layer processing with bracket-based filtering
|
|
246
|
+
contextPercent = estimateContextPercent(promptCount);
|
|
247
|
+
bracket = calculateBracket(contextPercent);
|
|
248
|
+
const layerConfig = getActiveLayers(bracket);
|
|
249
|
+
tokenBudget = getTokenBudget(bracket);
|
|
250
|
+
|
|
251
|
+
// Guard: no layer config (invalid bracket — should not happen)
|
|
252
|
+
if (!layerConfig) {
|
|
253
|
+
metrics.totalEnd = process.hrtime.bigint();
|
|
254
|
+
return { xml: '', metrics: metrics.getSummary() };
|
|
255
|
+
}
|
|
256
|
+
activeLayers = layerConfig.layers;
|
|
257
|
+
} else {
|
|
258
|
+
// NOG-18: Simplified — always load L0-L2, skip bracket calculation.
|
|
259
|
+
// L3-L7 produced 0 rules (require session context that never exists).
|
|
260
|
+
// Bracket management replaced by native /compact.
|
|
261
|
+
contextPercent = estimateContextPercent(promptCount);
|
|
262
|
+
bracket = calculateBracket(contextPercent);
|
|
263
|
+
activeLayers = DEFAULT_ACTIVE_LAYERS;
|
|
264
|
+
tokenBudget = getTokenBudget(bracket);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 2. Execute layers sequentially
|
|
268
|
+
const results = [];
|
|
269
|
+
const previousLayers = [];
|
|
270
|
+
|
|
271
|
+
for (const layer of this.layers) {
|
|
272
|
+
// Check bracket filter
|
|
273
|
+
if (!activeLayers.includes(layer.layer)) {
|
|
274
|
+
metrics.skipLayer(layer.name, `Not active in ${bracket}`);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Check hard pipeline timeout (convert hrtime to ms for comparison)
|
|
279
|
+
if (Number(process.hrtime.bigint() - metrics.totalStart) / 1e6 > PIPELINE_TIMEOUT_MS) {
|
|
280
|
+
// Log remaining layers as skipped
|
|
281
|
+
const remaining = this.layers.slice(this.layers.indexOf(layer));
|
|
282
|
+
for (const r of remaining) {
|
|
283
|
+
if (activeLayers.includes(r.layer) && !metrics.layers[r.name]) {
|
|
284
|
+
metrics.skipLayer(r.name, 'Pipeline timeout');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Execute layer via safe wrapper
|
|
291
|
+
metrics.startLayer(layer.name);
|
|
292
|
+
const context = buildLayerContext({
|
|
293
|
+
prompt,
|
|
294
|
+
session: session || {},
|
|
295
|
+
config: mergedConfig,
|
|
296
|
+
synapsePath: this.synapsePath,
|
|
297
|
+
manifest: mergedConfig.manifest || {},
|
|
298
|
+
previousLayers,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const result = layer._safeProcess(context);
|
|
302
|
+
|
|
303
|
+
if (result && Array.isArray(result.rules)) {
|
|
304
|
+
metrics.endLayer(layer.name, result.rules.length);
|
|
305
|
+
results.push(result);
|
|
306
|
+
previousLayers.push(result);
|
|
307
|
+
} else if (result === null || result === undefined) {
|
|
308
|
+
metrics.skipLayer(layer.name, 'Returned null');
|
|
309
|
+
} else {
|
|
310
|
+
metrics.skipLayer(layer.name, 'Invalid result format');
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// 3. Memory bridge (SYN-10) — feature-gated MIS consumer
|
|
315
|
+
if (needsMemoryHints(bracket)) {
|
|
316
|
+
const hints = await this.memoryBridge.getMemoryHints(
|
|
317
|
+
(session && session.activeAgent) || (session && session.active_agent) || '',
|
|
318
|
+
bracket,
|
|
319
|
+
tokenBudget,
|
|
320
|
+
);
|
|
321
|
+
if (hints.length > 0) {
|
|
322
|
+
const memoryResult = { layer: 'memory', rules: hints, metadata: { layer: 'memory', source: 'memory' } };
|
|
323
|
+
results.push(memoryResult);
|
|
324
|
+
previousLayers.push(memoryResult);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
metrics.totalEnd = process.hrtime.bigint();
|
|
329
|
+
const summary = metrics.getSummary();
|
|
330
|
+
|
|
331
|
+
// Persist hook metrics (fire-and-forget)
|
|
332
|
+
this._persistHookMetrics(summary, bracket, mergedConfig);
|
|
333
|
+
|
|
334
|
+
// 4. Format output
|
|
335
|
+
const xml = formatSynapseRules(
|
|
336
|
+
results,
|
|
337
|
+
bracket,
|
|
338
|
+
contextPercent,
|
|
339
|
+
session || {},
|
|
340
|
+
mergedConfig.devmode === true,
|
|
341
|
+
summary,
|
|
342
|
+
tokenBudget,
|
|
343
|
+
needsHandoffWarning(bracket),
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
return { xml, metrics: summary, bracket };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Persist hook metrics to .synapse/metrics/hook-metrics.json (fire-and-forget).
|
|
351
|
+
* SYN-14: Includes hookBootMs from _hookBootTime passed via processConfig.
|
|
352
|
+
* @param {object} summary - Pipeline metrics summary
|
|
353
|
+
* @param {string} bracket - Context bracket
|
|
354
|
+
* @param {object} [config] - Merged config (may contain _hookBootTime bigint)
|
|
355
|
+
*/
|
|
356
|
+
_persistHookMetrics(summary, bracket, config) {
|
|
357
|
+
try {
|
|
358
|
+
const synapsePath = this.synapsePath;
|
|
359
|
+
if (!synapsePath || !fs.existsSync(synapsePath)) return;
|
|
360
|
+
const metricsDir = path.join(synapsePath, 'metrics');
|
|
361
|
+
if (!fs.existsSync(metricsDir)) {
|
|
362
|
+
fs.mkdirSync(metricsDir, { recursive: true });
|
|
363
|
+
}
|
|
364
|
+
// SYN-14: Calculate hook boot time if _hookBootTime was passed
|
|
365
|
+
const hookBootTime = config && config._hookBootTime;
|
|
366
|
+
const hookBootMs = hookBootTime ? Number(process.hrtime.bigint() - hookBootTime) / 1e6 : 0;
|
|
367
|
+
const data = {
|
|
368
|
+
totalDuration: summary.total_ms,
|
|
369
|
+
hookBootMs,
|
|
370
|
+
bracket,
|
|
371
|
+
layersLoaded: summary.layers_loaded,
|
|
372
|
+
layersSkipped: summary.layers_skipped,
|
|
373
|
+
layersErrored: summary.layers_errored,
|
|
374
|
+
totalRules: summary.total_rules,
|
|
375
|
+
perLayer: {},
|
|
376
|
+
timestamp: new Date().toISOString(),
|
|
377
|
+
};
|
|
378
|
+
// Convert per_layer to serializable format (strip bigint start/end)
|
|
379
|
+
for (const [name, info] of Object.entries(summary.per_layer)) {
|
|
380
|
+
data.perLayer[name] = {
|
|
381
|
+
duration: info.duration || 0,
|
|
382
|
+
status: info.status || 'unknown',
|
|
383
|
+
rules: info.rules || 0,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
fs.writeFileSync(
|
|
387
|
+
path.join(metricsDir, 'hook-metrics.json'),
|
|
388
|
+
JSON.stringify(data, null, 2), 'utf8',
|
|
389
|
+
);
|
|
390
|
+
} catch {
|
|
391
|
+
// Fire-and-forget: never block the hook pipeline
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
module.exports = {
|
|
397
|
+
SynapseEngine,
|
|
398
|
+
PipelineMetrics,
|
|
399
|
+
PIPELINE_TIMEOUT_MS,
|
|
400
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L0 Constitution Layer Processor
|
|
3
|
+
*
|
|
4
|
+
* Injects the 6 Constitutional articles as NON-NEGOTIABLE rules.
|
|
5
|
+
* ALWAYS_ON — processes regardless of session state.
|
|
6
|
+
* Rules are loaded from the .synapse/constitution domain file.
|
|
7
|
+
*
|
|
8
|
+
* @module core/synapse/layers/l0-constitution
|
|
9
|
+
* @version 1.0.0
|
|
10
|
+
* @created Story SYN-4 - Layer Processors L0-L3
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const { loadDomainFile } = require('../domain/domain-loader');
|
|
15
|
+
const LayerProcessor = require('./layer-processor');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* L0 Constitution Processor
|
|
19
|
+
*
|
|
20
|
+
* Loads constitution domain file and validates nonNegotiable flag in manifest.
|
|
21
|
+
* Returns all constitutional rules as an array of strings.
|
|
22
|
+
*
|
|
23
|
+
* @extends LayerProcessor
|
|
24
|
+
*/
|
|
25
|
+
class L0ConstitutionProcessor extends LayerProcessor {
|
|
26
|
+
constructor() {
|
|
27
|
+
super({ name: 'constitution', layer: 0, timeout: 5 });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Load constitution rules from domain file.
|
|
32
|
+
*
|
|
33
|
+
* ALWAYS_ON: processes regardless of session state.
|
|
34
|
+
* Validates that the constitution domain has nonNegotiable: true in manifest.
|
|
35
|
+
*
|
|
36
|
+
* @param {object} context
|
|
37
|
+
* @param {string} context.prompt - Current prompt text
|
|
38
|
+
* @param {object} context.session - Session state
|
|
39
|
+
* @param {object} context.config - Config with synapsePath and manifest
|
|
40
|
+
* @param {object[]} context.previousLayers - Results from previous layers
|
|
41
|
+
* @returns {{ rules: string[], metadata: object } | null}
|
|
42
|
+
*/
|
|
43
|
+
process(context) {
|
|
44
|
+
const { config } = context;
|
|
45
|
+
const { synapsePath, manifest } = config;
|
|
46
|
+
|
|
47
|
+
// Find constitution domain in manifest
|
|
48
|
+
const domainKey = Object.keys(manifest.domains || {})
|
|
49
|
+
.find(k => k.toUpperCase() === 'CONSTITUTION');
|
|
50
|
+
|
|
51
|
+
const domain = domainKey ? manifest.domains[domainKey] : null;
|
|
52
|
+
|
|
53
|
+
// Determine domain file path
|
|
54
|
+
const domainFile = domain && domain.file
|
|
55
|
+
? path.join(synapsePath, domain.file)
|
|
56
|
+
: path.join(synapsePath, 'constitution');
|
|
57
|
+
|
|
58
|
+
// Load rules from domain file
|
|
59
|
+
const rules = loadDomainFile(domainFile);
|
|
60
|
+
|
|
61
|
+
// Graceful degradation: no rules found
|
|
62
|
+
if (!rules || rules.length === 0) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Validate nonNegotiable flag
|
|
67
|
+
const nonNegotiable = domain ? domain.nonNegotiable === true : false;
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
rules,
|
|
71
|
+
metadata: {
|
|
72
|
+
layer: 0,
|
|
73
|
+
source: 'constitution',
|
|
74
|
+
nonNegotiable,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = L0ConstitutionProcessor;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L1 Global Layer Processor
|
|
3
|
+
*
|
|
4
|
+
* Injects universal rules from two domain files:
|
|
5
|
+
* - .synapse/global — global rules applying to all contexts
|
|
6
|
+
* - .synapse/context — bracket-specific rules from context tracker
|
|
7
|
+
*
|
|
8
|
+
* ALWAYS_ON — processes regardless of session state.
|
|
9
|
+
* Combines both sources: global first, context second.
|
|
10
|
+
*
|
|
11
|
+
* @module core/synapse/layers/l1-global
|
|
12
|
+
* @version 1.0.0
|
|
13
|
+
* @created Story SYN-4 - Layer Processors L0-L3
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const { loadDomainFile } = require('../domain/domain-loader');
|
|
18
|
+
const LayerProcessor = require('./layer-processor');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* L1 Global Processor
|
|
22
|
+
*
|
|
23
|
+
* Loads global and context domain files and combines their rules.
|
|
24
|
+
* If one file is missing, includes rules from the other only.
|
|
25
|
+
*
|
|
26
|
+
* @extends LayerProcessor
|
|
27
|
+
*/
|
|
28
|
+
class L1GlobalProcessor extends LayerProcessor {
|
|
29
|
+
constructor() {
|
|
30
|
+
super({ name: 'global', layer: 1, timeout: 10 });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Load global and context rules.
|
|
35
|
+
*
|
|
36
|
+
* ALWAYS_ON: processes regardless of session state.
|
|
37
|
+
* Combines rules from both domain files (global first, context second).
|
|
38
|
+
*
|
|
39
|
+
* @param {object} context
|
|
40
|
+
* @param {string} context.prompt - Current prompt text
|
|
41
|
+
* @param {object} context.session - Session state
|
|
42
|
+
* @param {object} context.config - Config with synapsePath and manifest
|
|
43
|
+
* @param {object[]} context.previousLayers - Results from previous layers
|
|
44
|
+
* @returns {{ rules: string[], metadata: object } | null}
|
|
45
|
+
*/
|
|
46
|
+
process(context) {
|
|
47
|
+
const { config } = context;
|
|
48
|
+
const { synapsePath, manifest } = config;
|
|
49
|
+
|
|
50
|
+
// Resolve file paths from manifest or use defaults
|
|
51
|
+
const globalDomain = this._findDomain(manifest, 'GLOBAL');
|
|
52
|
+
const contextDomain = this._findDomain(manifest, 'CONTEXT');
|
|
53
|
+
|
|
54
|
+
const globalFile = globalDomain && globalDomain.file
|
|
55
|
+
? path.join(synapsePath, globalDomain.file)
|
|
56
|
+
: path.join(synapsePath, 'global');
|
|
57
|
+
|
|
58
|
+
const contextFile = contextDomain && contextDomain.file
|
|
59
|
+
? path.join(synapsePath, contextDomain.file)
|
|
60
|
+
: path.join(synapsePath, 'context');
|
|
61
|
+
|
|
62
|
+
// Load rules from both domain files
|
|
63
|
+
const globalRules = loadDomainFile(globalFile);
|
|
64
|
+
const contextRules = loadDomainFile(contextFile);
|
|
65
|
+
|
|
66
|
+
// Combine: global first, context second
|
|
67
|
+
const rules = [...globalRules, ...contextRules];
|
|
68
|
+
|
|
69
|
+
// Graceful degradation: no rules from either file
|
|
70
|
+
if (rules.length === 0) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const sources = [];
|
|
75
|
+
if (globalRules.length > 0) sources.push('global');
|
|
76
|
+
if (contextRules.length > 0) sources.push('context');
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
rules,
|
|
80
|
+
metadata: {
|
|
81
|
+
layer: 1,
|
|
82
|
+
sources,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Find a domain entry in the manifest by uppercase key.
|
|
89
|
+
*
|
|
90
|
+
* @param {object} manifest - Parsed manifest
|
|
91
|
+
* @param {string} name - Domain name to find (case-insensitive)
|
|
92
|
+
* @returns {object|null} Domain entry or null
|
|
93
|
+
* @private
|
|
94
|
+
*/
|
|
95
|
+
_findDomain(manifest, name) {
|
|
96
|
+
const key = Object.keys(manifest.domains || {})
|
|
97
|
+
.find(k => k.toUpperCase() === name.toUpperCase());
|
|
98
|
+
return key ? manifest.domains[key] : null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = L1GlobalProcessor;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L2 Agent-Scoped Layer Processor
|
|
3
|
+
*
|
|
4
|
+
* Injects agent-specific rules based on the currently active agent.
|
|
5
|
+
* Detects the active agent from session.active_agent.id and finds
|
|
6
|
+
* the matching domain via agentTrigger in the manifest.
|
|
7
|
+
*
|
|
8
|
+
* Authority boundaries (rules containing 'AUTH') are always included.
|
|
9
|
+
*
|
|
10
|
+
* @module core/synapse/layers/l2-agent
|
|
11
|
+
* @version 1.0.0
|
|
12
|
+
* @created Story SYN-4 - Layer Processors L0-L3
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { loadDomainFile } = require('../domain/domain-loader');
|
|
17
|
+
const LayerProcessor = require('./layer-processor');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* L2 Agent-Scoped Processor
|
|
21
|
+
*
|
|
22
|
+
* Loads agent-specific domain file when an agent is active.
|
|
23
|
+
* Returns null if no agent is active or no matching domain found.
|
|
24
|
+
*
|
|
25
|
+
* @extends LayerProcessor
|
|
26
|
+
*/
|
|
27
|
+
class L2AgentProcessor extends LayerProcessor {
|
|
28
|
+
constructor() {
|
|
29
|
+
super({ name: 'agent', layer: 2, timeout: 15 });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Load agent-specific rules based on active agent.
|
|
34
|
+
*
|
|
35
|
+
* Detection flow:
|
|
36
|
+
* 1. Get active agent ID from session.active_agent.id
|
|
37
|
+
* 2. Find domain with matching agentTrigger in manifest
|
|
38
|
+
* 3. Load domain file via domain-loader
|
|
39
|
+
* 4. Filter authority boundaries (rules containing 'AUTH')
|
|
40
|
+
*
|
|
41
|
+
* @param {object} context
|
|
42
|
+
* @param {string} context.prompt - Current prompt text
|
|
43
|
+
* @param {object} context.session - Session state (SYN-2 schema)
|
|
44
|
+
* @param {object} context.config - Config with synapsePath and manifest
|
|
45
|
+
* @param {object[]} context.previousLayers - Results from previous layers
|
|
46
|
+
* @returns {{ rules: string[], metadata: object } | null}
|
|
47
|
+
*/
|
|
48
|
+
process(context) {
|
|
49
|
+
const { session, config } = context;
|
|
50
|
+
const { manifest, synapsePath } = config;
|
|
51
|
+
|
|
52
|
+
// 1. Get active agent ID
|
|
53
|
+
const agentId = session.active_agent?.id;
|
|
54
|
+
if (!agentId) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 2. Find domain with matching agentTrigger
|
|
59
|
+
const domainKey = Object.keys(manifest.domains || {})
|
|
60
|
+
.find(k => manifest.domains[k].agentTrigger === agentId);
|
|
61
|
+
|
|
62
|
+
if (!domainKey) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 3. Load domain file
|
|
67
|
+
const domain = manifest.domains[domainKey];
|
|
68
|
+
const domainFile = domain.file
|
|
69
|
+
? path.join(synapsePath, domain.file)
|
|
70
|
+
: path.join(synapsePath, `agent-${agentId}`);
|
|
71
|
+
|
|
72
|
+
const rules = loadDomainFile(domainFile);
|
|
73
|
+
|
|
74
|
+
// Graceful degradation: domain file missing or empty
|
|
75
|
+
if (!rules || rules.length === 0) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 4. Check for authority boundaries
|
|
80
|
+
const hasAuthority = rules.some(r => r.toUpperCase().includes('AUTH'));
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
rules,
|
|
84
|
+
metadata: {
|
|
85
|
+
layer: 2,
|
|
86
|
+
source: `agent-${agentId}`,
|
|
87
|
+
agentId,
|
|
88
|
+
hasAuthority,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = L2AgentProcessor;
|