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.
Files changed (90) hide show
  1. package/.auroq-core/constitution.md +153 -0
  2. package/.auroq-core/core/synapse/context/context-builder.js +34 -0
  3. package/.auroq-core/core/synapse/context/context-tracker.js +198 -0
  4. package/.auroq-core/core/synapse/diagnostics/collectors/consistency-collector.js +168 -0
  5. package/.auroq-core/core/synapse/diagnostics/collectors/hook-collector.js +129 -0
  6. package/.auroq-core/core/synapse/diagnostics/collectors/manifest-collector.js +82 -0
  7. package/.auroq-core/core/synapse/diagnostics/collectors/output-analyzer.js +134 -0
  8. package/.auroq-core/core/synapse/diagnostics/collectors/pipeline-collector.js +75 -0
  9. package/.auroq-core/core/synapse/diagnostics/collectors/quality-collector.js +252 -0
  10. package/.auroq-core/core/synapse/diagnostics/collectors/relevance-matrix.js +174 -0
  11. package/.auroq-core/core/synapse/diagnostics/collectors/safe-read-json.js +31 -0
  12. package/.auroq-core/core/synapse/diagnostics/collectors/session-collector.js +102 -0
  13. package/.auroq-core/core/synapse/diagnostics/collectors/timing-collector.js +126 -0
  14. package/.auroq-core/core/synapse/diagnostics/collectors/uap-collector.js +83 -0
  15. package/.auroq-core/core/synapse/diagnostics/report-formatter.js +484 -0
  16. package/.auroq-core/core/synapse/diagnostics/synapse-diagnostics.js +95 -0
  17. package/.auroq-core/core/synapse/domain/domain-loader.js +322 -0
  18. package/.auroq-core/core/synapse/engine.js +400 -0
  19. package/.auroq-core/core/synapse/layers/l0-constitution.js +80 -0
  20. package/.auroq-core/core/synapse/layers/l1-global.js +102 -0
  21. package/.auroq-core/core/synapse/layers/l2-agent.js +94 -0
  22. package/.auroq-core/core/synapse/layers/l3-workflow.js +94 -0
  23. package/.auroq-core/core/synapse/layers/l4-task.js +83 -0
  24. package/.auroq-core/core/synapse/layers/l5-squad.js +244 -0
  25. package/.auroq-core/core/synapse/layers/l6-keyword.js +154 -0
  26. package/.auroq-core/core/synapse/layers/l7-star-command.js +169 -0
  27. package/.auroq-core/core/synapse/layers/layer-processor.js +82 -0
  28. package/.auroq-core/core/synapse/memory/memory-bridge.js +220 -0
  29. package/.auroq-core/core/synapse/memory/synapse-memory-provider.js +201 -0
  30. package/.auroq-core/core/synapse/output/formatter.js +561 -0
  31. package/.auroq-core/core/synapse/runtime/hook-runtime.js +98 -0
  32. package/.auroq-core/core/synapse/scripts/generate-constitution.js +204 -0
  33. package/.auroq-core/core/synapse/session/session-manager.js +404 -0
  34. package/.auroq-core/core/synapse/utils/atomic-write.js +79 -0
  35. package/.auroq-core/core/synapse/utils/paths.js +57 -0
  36. package/.auroq-core/core/synapse/utils/tokens.js +25 -0
  37. package/.auroq-core/core-config.yaml +80 -0
  38. package/.auroq-core/development/sistema-gestao-projetos.md +193 -0
  39. package/.auroq-core/development/sistema-memoria.md +157 -0
  40. package/.auroq-core/dna-operacional.md +134 -0
  41. package/.claude/CLAUDE.md +399 -0
  42. package/.claude/commands/AuroqOS/agents/forge.md +374 -0
  43. package/.claude/commands/AuroqOS/agents/ops.md +277 -0
  44. package/.claude/commands/companion.md +7 -0
  45. package/.claude/hooks/precompact-session-digest.cjs +46 -0
  46. package/.claude/hooks/synapse-engine.cjs +99 -0
  47. package/.claude/rules/agent-authority.md +79 -0
  48. package/.claude/rules/agent-handoff.md +97 -0
  49. package/.claude/rules/commit-inteligente.md +86 -0
  50. package/.claude/rules/dna-operacional.md +53 -0
  51. package/.claude/rules/evolucao-incremental.md +43 -0
  52. package/.claude/rules/mcp-usage.md +38 -0
  53. package/.claude/rules/memoria-inteligente.md +65 -0
  54. package/.claude/rules/project-tracker.md +49 -0
  55. package/.claude/rules/tool-response-filtering.md +57 -0
  56. package/.claude/settings.json +1 -0
  57. package/.claude/settings.local.json +33 -0
  58. package/.env.example +22 -0
  59. package/.gitignore +23 -0
  60. package/.synapse/constitution +21 -0
  61. package/.synapse/context +8 -0
  62. package/.synapse/global +10 -0
  63. package/.synapse/manifest +35 -0
  64. package/README.md +67 -0
  65. package/agents/companion/agents/companion.md +306 -0
  66. package/agents/companion/data/contexto-dinamico.md +23 -0
  67. package/agents/companion/data/demandas-backlog.md +19 -0
  68. package/agents/companion/data/log-decisoes.md +11 -0
  69. package/agents/companion/data/padroes-observados.md +10 -0
  70. package/agents/companion/knowledge/companion-foundation.md +49 -0
  71. package/agents/companion/knowledge/expert-essencial.md +30 -0
  72. package/agents/companion/knowledge/modus-operandi.md +161 -0
  73. package/agents/companion/tasks/new-project.md +111 -0
  74. package/agents/companion/tasks/save-memory.md +73 -0
  75. package/agents/companion/tasks/start.md +136 -0
  76. package/agents/companion/tasks/weekly-review.md +82 -0
  77. package/bin/auroq-os.js +346 -0
  78. package/business/campanhas/_template/BRIEFING.md +45 -0
  79. package/business/campanhas/_template/CHECKLIST.md +29 -0
  80. package/business/campanhas/_template/DIARIO.md +15 -0
  81. package/business/campanhas/_template/RETROVISOR.md +34 -0
  82. package/business/cockpit.md +72 -0
  83. package/business/templates/tracker-template.md +71 -0
  84. package/docs/knowledge/expert-business/posicionamento.md +27 -0
  85. package/docs/knowledge/expert-business/publico-alvo.md +39 -0
  86. package/docs/knowledge/expert-mind/historia.md +27 -0
  87. package/docs/knowledge/expert-mind/identidade.md +23 -0
  88. package/docs/knowledge/expert-mind/tom-de-voz.md +30 -0
  89. package/docs/knowledge/expert-mind/valores.md +32 -0
  90. 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;