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,561 @@
1
+ /**
2
+ * SYNAPSE Output Formatter
3
+ *
4
+ * Converts layer processor results into the <synapse-rules> XML block
5
+ * that is injected into each user prompt. Enforces section ordering
6
+ * per DESIGN doc section 14 and applies token budget truncation.
7
+ *
8
+ * @module core/synapse/output/formatter
9
+ * @version 1.0.0
10
+ * @created Story SYN-6 - SynapseEngine Orchestrator + Output Formatter
11
+ */
12
+
13
+ const { estimateTokens } = require('../utils/tokens');
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Section ordering (DESIGN doc section 14)
17
+ // ---------------------------------------------------------------------------
18
+
19
+ /**
20
+ * Ordered list of section identifiers matching layer outputs.
21
+ * Sections are rendered in this order; null results are omitted.
22
+ *
23
+ * Truncation removes from the END of this array first (SUMMARY, then
24
+ * KEYWORD, then SQUAD, etc.) to preserve highest-priority sections.
25
+ */
26
+ const SECTION_ORDER = [
27
+ 'CONTEXT_BRACKET',
28
+ 'CONSTITUTION',
29
+ 'AGENT',
30
+ 'WORKFLOW',
31
+ 'TASK',
32
+ 'SQUAD',
33
+ 'KEYWORD',
34
+ 'MEMORY_HINTS',
35
+ 'STAR_COMMANDS',
36
+ 'DEVMODE',
37
+ 'SUMMARY',
38
+ ];
39
+
40
+ /**
41
+ * Map layer names to section identifiers.
42
+ */
43
+ const LAYER_TO_SECTION = {
44
+ constitution: 'CONSTITUTION',
45
+ global: 'CONTEXT_BRACKET', // global rules go into bracket section
46
+ agent: 'AGENT',
47
+ workflow: 'WORKFLOW',
48
+ task: 'TASK',
49
+ squad: 'SQUAD',
50
+ keyword: 'KEYWORD',
51
+ memory: 'MEMORY_HINTS',
52
+ 'star-command': 'STAR_COMMANDS',
53
+ };
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // Section Formatters
57
+ // ---------------------------------------------------------------------------
58
+
59
+ /**
60
+ * Format the CONTEXT BRACKET header section.
61
+ *
62
+ * @param {string} bracket - Current bracket name
63
+ * @param {number} contextPercent - Remaining context %
64
+ * @param {object[]} globalResults - Results from global/context layers
65
+ * @returns {string}
66
+ */
67
+ function formatContextBracket(bracket, contextPercent, globalResults) {
68
+ const lines = [`[CONTEXT BRACKET]\nCONTEXT BRACKET: [${bracket}] (${contextPercent.toFixed(1)}% remaining)`];
69
+
70
+ // Include global/context rules if present
71
+ for (const result of globalResults) {
72
+ if (result && result.rules && result.rules.length > 0) {
73
+ lines.push(`[${bracket}] CONTEXT RULES:`);
74
+ result.rules.forEach((rule, i) => {
75
+ lines.push(` ${i + 1}. ${rule}`);
76
+ });
77
+ }
78
+ }
79
+
80
+ return lines.join('\n');
81
+ }
82
+
83
+ /**
84
+ * Format the CONSTITUTION section.
85
+ *
86
+ * @param {object} result - Layer result { rules, metadata }
87
+ * @returns {string}
88
+ */
89
+ function formatConstitution(result) {
90
+ const lines = ['[CONSTITUTION] (NON-NEGOTIABLE)'];
91
+ for (const rule of result.rules) {
92
+ lines.push(` ${rule}`);
93
+ }
94
+ return lines.join('\n');
95
+ }
96
+
97
+ /**
98
+ * Format the AGENT section.
99
+ *
100
+ * @param {object} result - Layer result { rules, metadata }
101
+ * @returns {string}
102
+ */
103
+ function formatAgent(result) {
104
+ const meta = result.metadata || {};
105
+ const agentId = meta.agentId || meta.source || 'unknown';
106
+ const domain = meta.domain || meta.source || '';
107
+ const lines = [`[ACTIVE AGENT: @${agentId}]`];
108
+
109
+ if (domain) {
110
+ lines.push(` DOMAIN: ${domain}`);
111
+ }
112
+
113
+ if (meta.authority && Array.isArray(meta.authority)) {
114
+ lines.push(' AUTHORITY BOUNDARIES:');
115
+ for (const auth of meta.authority) {
116
+ lines.push(` - ${auth}`);
117
+ }
118
+ }
119
+
120
+ if (result.rules.length > 0) {
121
+ lines.push(' RULES:');
122
+ result.rules.forEach((rule, i) => {
123
+ lines.push(` ${i}. ${rule}`);
124
+ });
125
+ }
126
+
127
+ return lines.join('\n');
128
+ }
129
+
130
+ /**
131
+ * Format the WORKFLOW section.
132
+ *
133
+ * @param {object} result - Layer result { rules, metadata }
134
+ * @returns {string}
135
+ */
136
+ function formatWorkflow(result) {
137
+ const meta = result.metadata || {};
138
+ const workflowId = meta.workflowId || meta.source || 'unknown';
139
+ const phase = meta.phase || '';
140
+ const lines = [`[ACTIVE WORKFLOW: ${workflowId}]`];
141
+
142
+ if (phase) {
143
+ lines.push(` PHASE: ${phase}`);
144
+ }
145
+
146
+ if (result.rules.length > 0) {
147
+ lines.push(' RULES:');
148
+ result.rules.forEach((rule, i) => {
149
+ lines.push(` ${i}. ${rule}`);
150
+ });
151
+ }
152
+
153
+ return lines.join('\n');
154
+ }
155
+
156
+ /**
157
+ * Format the TASK section.
158
+ *
159
+ * @param {object} result - Layer result { rules, metadata }
160
+ * @returns {string}
161
+ */
162
+ function formatTask(result) {
163
+ const meta = result.metadata || {};
164
+ const lines = ['[TASK CONTEXT]'];
165
+
166
+ if (meta.taskId) {
167
+ lines.push(` Active Task: ${meta.taskId}`);
168
+ }
169
+ if (meta.storyId) {
170
+ lines.push(` Story: ${meta.storyId}`);
171
+ }
172
+
173
+ for (const rule of result.rules) {
174
+ lines.push(` ${rule}`);
175
+ }
176
+
177
+ return lines.join('\n');
178
+ }
179
+
180
+ /**
181
+ * Format the SQUAD section.
182
+ *
183
+ * @param {object} result - Layer result { rules, metadata }
184
+ * @returns {string}
185
+ */
186
+ function formatSquad(result) {
187
+ const meta = result.metadata || {};
188
+ const squadName = meta.squadName || meta.source || 'unknown';
189
+ const lines = [`[SQUAD: ${squadName}]`];
190
+
191
+ for (const rule of result.rules) {
192
+ lines.push(` ${rule}`);
193
+ }
194
+
195
+ return lines.join('\n');
196
+ }
197
+
198
+ /**
199
+ * Format the KEYWORD section.
200
+ *
201
+ * @param {object} result - Layer result { rules, metadata }
202
+ * @returns {string}
203
+ */
204
+ function formatKeyword(result) {
205
+ const meta = result.metadata || {};
206
+ const lines = ['[KEYWORD MATCHES]'];
207
+
208
+ if (meta.matches && Array.isArray(meta.matches)) {
209
+ for (const match of meta.matches) {
210
+ lines.push(` "${match.keyword}" matched ${match.domain} (${match.reason || 'keyword match'})`);
211
+ }
212
+ }
213
+
214
+ for (const rule of result.rules) {
215
+ lines.push(` ${rule}`);
216
+ }
217
+
218
+ return lines.join('\n');
219
+ }
220
+
221
+ /**
222
+ * Format the STAR-COMMANDS section.
223
+ *
224
+ * @param {object} result - Layer result { rules, metadata }
225
+ * @returns {string}
226
+ */
227
+ function formatStarCommands(result) {
228
+ const meta = result.metadata || {};
229
+ const command = meta.command || meta.source || 'unknown';
230
+ const lines = [
231
+ '[STAR-COMMANDS]',
232
+ '============================================================',
233
+ `[*${command}] COMMAND:`,
234
+ ];
235
+
236
+ result.rules.forEach((rule, i) => {
237
+ lines.push(` ${i}. ${rule}`);
238
+ });
239
+
240
+ lines.push('============================================================');
241
+ return lines.join('\n');
242
+ }
243
+
244
+ // ---------------------------------------------------------------------------
245
+ // Memory Hints Section (SYN-10)
246
+ // ---------------------------------------------------------------------------
247
+
248
+ /**
249
+ * Format the MEMORY HINTS section.
250
+ *
251
+ * Only included when hints array is non-empty.
252
+ * Each hint displays source, relevance, and content.
253
+ *
254
+ * @param {object} result - Layer result { rules (hint objects), metadata }
255
+ * @returns {string}
256
+ */
257
+ function formatMemoryHints(result) {
258
+ const lines = ['[MEMORY HINTS]'];
259
+
260
+ for (const hint of result.rules) {
261
+ const source = hint.source || 'memory';
262
+ const relevance = typeof hint.relevance === 'number'
263
+ ? `${(hint.relevance * 100).toFixed(0)}%`
264
+ : '?%';
265
+ const content = hint.content || '';
266
+ lines.push(` [${source}] (relevance: ${relevance}) ${content}`);
267
+ }
268
+
269
+ return lines.join('\n');
270
+ }
271
+
272
+ // ---------------------------------------------------------------------------
273
+ // DEVMODE Section (DESIGN doc section 13)
274
+ // ---------------------------------------------------------------------------
275
+
276
+ /**
277
+ * Format the DEVMODE debug section.
278
+ *
279
+ * @param {string} bracket - Current bracket name
280
+ * @param {number} contextPercent - Remaining context %
281
+ * @param {object} session - Session state
282
+ * @param {object} metrics - Pipeline metrics summary
283
+ * @param {object[]} results - Layer results for domain analysis
284
+ * @returns {string}
285
+ */
286
+ function formatDevmode(bracket, contextPercent, session, metrics, results) {
287
+ const lines = [
288
+ '[DEVMODE STATUS]',
289
+ '---',
290
+ 'SYNAPSE DEVMODE',
291
+ '',
292
+ `Bracket: [${bracket}] (${contextPercent.toFixed(1)}% remaining)`,
293
+ '',
294
+ ];
295
+
296
+ // Layers loaded
297
+ const loaded = Object.entries(metrics.per_layer || {})
298
+ .filter(([, v]) => v.status === 'ok');
299
+ if (loaded.length > 0) {
300
+ lines.push('Layers Loaded:');
301
+ for (const [name, info] of loaded) {
302
+ const duration = info.duration != null ? `${info.duration}ms` : '?ms';
303
+ lines.push(` [L${info.layer != null ? info.layer : '?'}] ${name.toUpperCase()}: ${info.rules || 0} rules (${duration})`);
304
+ }
305
+ lines.push('');
306
+ }
307
+
308
+ // Layers skipped
309
+ const skipped = Object.entries(metrics.per_layer || {})
310
+ .filter(([, v]) => v.status === 'skipped');
311
+ if (skipped.length > 0) {
312
+ lines.push('Layers Skipped:');
313
+ for (const [name, info] of skipped) {
314
+ lines.push(` [${name.toUpperCase()}] ${info.reason || 'Unknown reason'}`);
315
+ }
316
+ lines.push('');
317
+ }
318
+
319
+ // Session info
320
+ if (session) {
321
+ lines.push('Session:');
322
+ if (session.id || session.sessionId) {
323
+ lines.push(` UUID: ${session.id || session.sessionId || 'unknown'}`);
324
+ }
325
+ if (session.active_agent) {
326
+ lines.push(` Agent: @${session.active_agent}`);
327
+ }
328
+ lines.push(` Prompts: ${session.prompt_count || 0} | Last bracket: ${bracket}`);
329
+ lines.push('');
330
+ }
331
+
332
+ // Pipeline metrics
333
+ lines.push('Pipeline Metrics:');
334
+ lines.push(` Total: ${metrics.total_ms}ms | Layers: ${metrics.layers_loaded}/${metrics.layers_loaded + metrics.layers_skipped + metrics.layers_errored} | Rules: ${metrics.total_rules}`);
335
+
336
+ // Available domains (not loaded) — based on results metadata
337
+ const loadedDomains = new Set(results.map(r => (r.metadata && r.metadata.source) || '').filter(Boolean));
338
+ if (loadedDomains.size > 0) {
339
+ lines.push('');
340
+ lines.push('Loaded Domains:');
341
+ for (const domain of loadedDomains) {
342
+ const result = results.find(r => r.metadata && r.metadata.source === domain);
343
+ const ruleCount = result ? result.rules.length : 0;
344
+ lines.push(` [${domain.toUpperCase()}] (${ruleCount} rules)`);
345
+ }
346
+ }
347
+
348
+ lines.push('---');
349
+ return lines.join('\n');
350
+ }
351
+
352
+ // ---------------------------------------------------------------------------
353
+ // Summary Section
354
+ // ---------------------------------------------------------------------------
355
+
356
+ /**
357
+ * Format the LOADED DOMAINS SUMMARY section.
358
+ *
359
+ * @param {object[]} results - All layer results
360
+ * @param {object} metrics - Pipeline metrics summary
361
+ * @returns {string}
362
+ */
363
+ function formatSummary(results, _metrics) {
364
+ const lines = ['[LOADED DOMAINS SUMMARY]', ' LOADED DOMAINS:'];
365
+
366
+ for (const result of results) {
367
+ if (!result || !result.rules || result.rules.length === 0) continue;
368
+ const meta = result.metadata || {};
369
+ const source = (meta.source || meta.domain || 'unknown').toUpperCase();
370
+ const reason = meta.activationReason || meta.reason || 'active layer';
371
+ lines.push(` [${source}] ${reason} (${result.rules.length} rules)`);
372
+ }
373
+
374
+ return lines.join('\n');
375
+ }
376
+
377
+ // ---------------------------------------------------------------------------
378
+ // Token Budget Enforcement
379
+ // ---------------------------------------------------------------------------
380
+
381
+ /**
382
+ * Enforce a token budget by removing sections from the end.
383
+ *
384
+ * Truncation order (last removed first): SUMMARY, KEYWORD, SQUAD,
385
+ * STAR_COMMANDS, TASK, WORKFLOW. CONSTITUTION and AGENT are never removed.
386
+ *
387
+ * @param {string[]} sections - Ordered section strings
388
+ * @param {string[]} sectionIds - Corresponding section identifiers
389
+ * @param {number} tokenBudget - Max tokens allowed
390
+ * @returns {string[]} Filtered sections within budget
391
+ */
392
+ function enforceTokenBudget(sections, sectionIds, tokenBudget) {
393
+ if (!tokenBudget || tokenBudget <= 0) {
394
+ return sections;
395
+ }
396
+
397
+ // Sections that should never be removed
398
+ const PROTECTED = new Set(['CONTEXT_BRACKET', 'CONSTITUTION', 'AGENT']);
399
+
400
+ // Truncation priority: remove from end first
401
+ const TRUNCATION_ORDER = [
402
+ 'SUMMARY',
403
+ 'KEYWORD',
404
+ 'MEMORY_HINTS',
405
+ 'SQUAD',
406
+ 'STAR_COMMANDS',
407
+ 'DEVMODE',
408
+ 'TASK',
409
+ 'WORKFLOW',
410
+ ];
411
+
412
+ const result = [...sections];
413
+ const ids = [...sectionIds];
414
+
415
+ let totalTokens = estimateTokens(result.join('\n\n'));
416
+
417
+ if (totalTokens <= tokenBudget) {
418
+ return result;
419
+ }
420
+
421
+ // Remove sections in truncation order
422
+ for (const sectionToRemove of TRUNCATION_ORDER) {
423
+ if (totalTokens <= tokenBudget) {
424
+ break;
425
+ }
426
+
427
+ const idx = ids.indexOf(sectionToRemove);
428
+ if (idx !== -1 && !PROTECTED.has(sectionToRemove)) {
429
+ result.splice(idx, 1);
430
+ ids.splice(idx, 1);
431
+ totalTokens = estimateTokens(result.join('\n\n'));
432
+ }
433
+ }
434
+
435
+ return result;
436
+ }
437
+
438
+ // ---------------------------------------------------------------------------
439
+ // Main Formatter
440
+ // ---------------------------------------------------------------------------
441
+
442
+ /**
443
+ * Section formatter dispatch table.
444
+ */
445
+ const SECTION_FORMATTERS = {
446
+ CONSTITUTION: formatConstitution,
447
+ AGENT: formatAgent,
448
+ WORKFLOW: formatWorkflow,
449
+ TASK: formatTask,
450
+ SQUAD: formatSquad,
451
+ KEYWORD: formatKeyword,
452
+ MEMORY_HINTS: formatMemoryHints,
453
+ STAR_COMMANDS: formatStarCommands,
454
+ };
455
+
456
+ /**
457
+ * Format all layer results into the <synapse-rules> XML output.
458
+ *
459
+ * @param {object[]} results - Layer results array
460
+ * @param {string} bracket - Current bracket name
461
+ * @param {number} contextPercent - Remaining context percentage
462
+ * @param {object} session - Session state
463
+ * @param {boolean} devmode - Whether DEVMODE is enabled
464
+ * @param {object} metrics - Pipeline metrics summary from PipelineMetrics.getSummary()
465
+ * @param {number} tokenBudget - Max tokens for this bracket
466
+ * @param {boolean} showHandoffWarning - Whether to include handoff warning
467
+ * @returns {string} Formatted <synapse-rules> XML string
468
+ */
469
+ function formatSynapseRules(results, bracket, contextPercent, session, devmode, metrics, tokenBudget, showHandoffWarning) {
470
+ if (!results || results.length === 0) {
471
+ return '';
472
+ }
473
+
474
+ // Categorize results by section
475
+ const sectionResults = {};
476
+ const globalResults = [];
477
+
478
+ for (const result of results) {
479
+ if (!result || !result.rules || result.rules.length === 0) {
480
+ continue;
481
+ }
482
+
483
+ const meta = result.metadata || {};
484
+ const layerName = meta.source || '';
485
+ const section = LAYER_TO_SECTION[layerName];
486
+
487
+ if (section === 'CONTEXT_BRACKET') {
488
+ globalResults.push(result);
489
+ } else if (section) {
490
+ sectionResults[section] = result;
491
+ } else {
492
+ // Fallback: try to match by layer number
493
+ const layerNum = meta.layer;
494
+ if (layerNum === 0) sectionResults['CONSTITUTION'] = result;
495
+ else if (layerNum === 1) globalResults.push(result);
496
+ else if (layerNum === 2) sectionResults['AGENT'] = result;
497
+ else if (layerNum === 3) sectionResults['WORKFLOW'] = result;
498
+ else if (layerNum === 4) sectionResults['TASK'] = result;
499
+ else if (layerNum === 5) sectionResults['SQUAD'] = result;
500
+ else if (layerNum === 6) sectionResults['KEYWORD'] = result;
501
+ else if (layerNum === 7) sectionResults['STAR_COMMANDS'] = result;
502
+ }
503
+ }
504
+
505
+ // Build sections in order
506
+ const sections = [];
507
+ const sectionIds = [];
508
+
509
+ // CONTEXT BRACKET — always first
510
+ sections.push(formatContextBracket(bracket, contextPercent, globalResults));
511
+ sectionIds.push('CONTEXT_BRACKET');
512
+
513
+ // Remaining sections in order (skip CONTEXT_BRACKET since already added)
514
+ for (const sectionId of SECTION_ORDER) {
515
+ if (sectionId === 'CONTEXT_BRACKET') continue;
516
+ if (sectionId === 'DEVMODE') continue;
517
+ if (sectionId === 'SUMMARY') continue;
518
+
519
+ const result = sectionResults[sectionId];
520
+ if (!result) continue;
521
+
522
+ const formatter = SECTION_FORMATTERS[sectionId];
523
+ if (formatter) {
524
+ sections.push(formatter(result));
525
+ sectionIds.push(sectionId);
526
+ }
527
+ }
528
+
529
+ // Handoff warning (CRITICAL bracket)
530
+ if (showHandoffWarning) {
531
+ sections.push('[HANDOFF WARNING]\n Context is nearly exhausted. Consider starting a new session to preserve quality.');
532
+ sectionIds.push('HANDOFF_WARNING');
533
+ }
534
+
535
+ // DEVMODE section (conditional)
536
+ if (devmode && metrics) {
537
+ sections.push(formatDevmode(bracket, contextPercent, session, metrics, results));
538
+ sectionIds.push('DEVMODE');
539
+ }
540
+
541
+ // SUMMARY — always last
542
+ if (results.length > 0) {
543
+ sections.push(formatSummary(results, metrics || {}));
544
+ sectionIds.push('SUMMARY');
545
+ }
546
+
547
+ // Token budget enforcement
548
+ const finalSections = enforceTokenBudget(sections, sectionIds, tokenBudget);
549
+
550
+ // Wrap in <synapse-rules> tags
551
+ const body = finalSections.join('\n\n');
552
+ return `<synapse-rules>\n\n${body}\n\n</synapse-rules>`;
553
+ }
554
+
555
+ module.exports = {
556
+ formatSynapseRules,
557
+ enforceTokenBudget,
558
+ estimateTokens,
559
+ SECTION_ORDER,
560
+ LAYER_TO_SECTION,
561
+ };
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+
6
+ const DEFAULT_STALE_TTL_HOURS = 168; // 7 days
7
+
8
+ /**
9
+ * Read stale session TTL from core-config.yaml.
10
+ * Falls back to DEFAULT_STALE_TTL_HOURS (168h = 7 days).
11
+ *
12
+ * @param {string} cwd - Working directory
13
+ * @returns {number} TTL in hours
14
+ */
15
+ function getStaleSessionTTL(cwd) {
16
+ try {
17
+ const yaml = require('js-yaml');
18
+ const configPath = path.join(cwd, '.auroq-core', 'core-config.yaml');
19
+ if (!fs.existsSync(configPath)) return DEFAULT_STALE_TTL_HOURS;
20
+ const config = yaml.load(fs.readFileSync(configPath, 'utf8'));
21
+ const ttl = config && config.synapse && config.synapse.session && config.synapse.session.staleTTLHours;
22
+ return typeof ttl === 'number' && ttl > 0 ? ttl : DEFAULT_STALE_TTL_HOURS;
23
+ } catch (_err) {
24
+ return DEFAULT_STALE_TTL_HOURS;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Resolve runtime dependencies for Synapse hook execution.
30
+ *
31
+ * On the first prompt of a session (prompt_count === 0), runs
32
+ * cleanStaleSessions() fire-and-forget to remove expired sessions.
33
+ *
34
+ * @param {{cwd?: string, session_id?: string, sessionId?: string}} input
35
+ * @returns {{
36
+ * engine: import('../engine').SynapseEngine,
37
+ * session: Object
38
+ * } | null}
39
+ */
40
+ function resolveHookRuntime(input) {
41
+ const cwd = input && input.cwd;
42
+ const sessionId = input && (input.session_id || input.sessionId);
43
+ if (!cwd || typeof cwd !== 'string') return null;
44
+
45
+ const synapsePath = path.join(cwd, '.synapse');
46
+ if (!fs.existsSync(synapsePath)) return null;
47
+
48
+ try {
49
+ const { loadSession, cleanStaleSessions } = require(
50
+ path.join(cwd, '.auroq-core', 'core', 'synapse', 'session', 'session-manager.js'),
51
+ );
52
+ const { SynapseEngine } = require(
53
+ path.join(cwd, '.auroq-core', 'core', 'synapse', 'engine.js'),
54
+ );
55
+
56
+ const sessionsDir = path.join(synapsePath, 'sessions');
57
+ const session = loadSession(sessionId, sessionsDir) || { prompt_count: 0 };
58
+ const engine = new SynapseEngine(synapsePath);
59
+
60
+ // AC3: Run cleanup on first prompt only (fire-and-forget)
61
+ if (session.prompt_count === 0) {
62
+ try {
63
+ const ttlHours = getStaleSessionTTL(cwd);
64
+ const removed = cleanStaleSessions(sessionsDir, ttlHours);
65
+ if (removed > 0 && process.env.DEBUG === '1') {
66
+ console.error(`[hook-runtime] Cleaned ${removed} stale session(s) (TTL: ${ttlHours}h)`);
67
+ }
68
+ } catch (_cleanupErr) {
69
+ // Fire-and-forget: never block hook execution
70
+ }
71
+ }
72
+
73
+ return { engine, session, sessionId, sessionsDir, cwd };
74
+ } catch (error) {
75
+ if (process.env.DEBUG === '1') {
76
+ console.error(`[hook-runtime] Failed to resolve runtime: ${error.message}`);
77
+ }
78
+ return null;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Normalize hook output payload shape.
84
+ * @param {string} xml
85
+ * @returns {{hookSpecificOutput: {additionalContext: string}}}
86
+ */
87
+ function buildHookOutput(xml) {
88
+ return {
89
+ hookSpecificOutput: {
90
+ additionalContext: xml || '',
91
+ },
92
+ };
93
+ }
94
+
95
+ module.exports = {
96
+ resolveHookRuntime,
97
+ buildHookOutput,
98
+ };