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,94 @@
1
+ /**
2
+ * L3 Workflow Layer Processor
3
+ *
4
+ * Injects workflow-specific rules based on the currently active workflow.
5
+ * Detects the active workflow from session.active_workflow.id and finds
6
+ * the matching domain via workflowTrigger in the manifest.
7
+ *
8
+ * Includes current phase metadata from session.active_workflow.current_phase.
9
+ *
10
+ * @module core/synapse/layers/l3-workflow
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
+ * L3 Workflow Processor
21
+ *
22
+ * Loads workflow-specific domain file when a workflow is active.
23
+ * Returns null if no workflow is active or no matching domain found.
24
+ *
25
+ * @extends LayerProcessor
26
+ */
27
+ class L3WorkflowProcessor extends LayerProcessor {
28
+ constructor() {
29
+ super({ name: 'workflow', layer: 3, timeout: 15 });
30
+ }
31
+
32
+ /**
33
+ * Load workflow-specific rules based on active workflow.
34
+ *
35
+ * Detection flow:
36
+ * 1. Get active workflow ID from session.active_workflow.id
37
+ * 2. Find domain with matching workflowTrigger in manifest
38
+ * 3. Load domain file via domain-loader
39
+ * 4. Include phase metadata from session
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 workflow ID
53
+ const workflowId = session.active_workflow?.id;
54
+ if (!workflowId) {
55
+ return null;
56
+ }
57
+
58
+ // 2. Find domain with matching workflowTrigger
59
+ const domainKey = Object.keys(manifest.domains || {})
60
+ .find(k => manifest.domains[k].workflowTrigger === workflowId);
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, `workflow-${workflowId}`);
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. Build metadata with phase info
80
+ const phase = session.active_workflow?.current_phase || null;
81
+
82
+ return {
83
+ rules,
84
+ metadata: {
85
+ layer: 3,
86
+ source: `workflow-${workflowId}`,
87
+ workflow: workflowId,
88
+ phase,
89
+ },
90
+ };
91
+ }
92
+ }
93
+
94
+ module.exports = L3WorkflowProcessor;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * L4 Task Layer Processor
3
+ *
4
+ * Injects task-specific context based on the currently active task.
5
+ * Detects the active task from session.active_task and formats
6
+ * task ID, story, and executor type as injectable rules.
7
+ *
8
+ * Returns null if no task is active (graceful skip).
9
+ *
10
+ * @module core/synapse/layers/l4-task
11
+ * @version 1.0.0
12
+ * @created Story SYN-5 - Layer Processors L4-L7
13
+ */
14
+
15
+ const LayerProcessor = require('./layer-processor');
16
+
17
+ /**
18
+ * L4 Task Processor
19
+ *
20
+ * Loads task context when a task is active in the session.
21
+ * Returns null if no active task or task has no ID.
22
+ *
23
+ * @extends LayerProcessor
24
+ */
25
+ class L4TaskProcessor extends LayerProcessor {
26
+ constructor() {
27
+ super({ name: 'task', layer: 4, timeout: 20 });
28
+ }
29
+
30
+ /**
31
+ * Load task-specific context as injectable rules.
32
+ *
33
+ * Detection flow:
34
+ * 1. Get active task from session.active_task
35
+ * 2. Validate task has an ID
36
+ * 3. Extract id, story, executor_type
37
+ * 4. Format as injectable rules
38
+ *
39
+ * @param {object} context
40
+ * @param {string} context.prompt - Current prompt text
41
+ * @param {object} context.session - Session state (SYN-2 schema)
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 { session } = context;
48
+
49
+ // 1. Get active task
50
+ const task = session.active_task;
51
+ if (!task || !task.id) {
52
+ return null;
53
+ }
54
+
55
+ // 2. Extract task fields
56
+ const taskId = task.id;
57
+ const story = task.story || null;
58
+ const executorType = task.executor_type || null;
59
+
60
+ // 3. Format as injectable rules
61
+ const rules = [];
62
+ rules.push(`Active Task: ${taskId}`);
63
+ if (story) {
64
+ rules.push(`Story: ${story}`);
65
+ }
66
+ if (executorType) {
67
+ rules.push(`Executor: ${executorType}`);
68
+ }
69
+
70
+ // 4. Return rules with metadata
71
+ return {
72
+ rules,
73
+ metadata: {
74
+ layer: 4,
75
+ taskId,
76
+ story,
77
+ executorType,
78
+ },
79
+ };
80
+ }
81
+ }
82
+
83
+ module.exports = L4TaskProcessor;
@@ -0,0 +1,244 @@
1
+ /**
2
+ * L5 Squad Layer Processor
3
+ *
4
+ * Discovers and merges domain rules from installed squads.
5
+ * Scans squads/ directory for .synapse/manifest files,
6
+ * namespaces domain keys with {SQUAD_NAME}_ prefix,
7
+ * and applies merge rules (extend/override/none).
8
+ *
9
+ * Implements a 60-second TTL cache to minimize filesystem scans.
10
+ * Prioritizes active squad domains when session.active_squad is set.
11
+ *
12
+ * @module core/synapse/layers/l5-squad
13
+ * @version 1.0.0
14
+ * @created Story SYN-5 - Layer Processors L4-L7
15
+ */
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const { parseManifest, loadDomainFile } = require('../domain/domain-loader');
20
+ const LayerProcessor = require('./layer-processor');
21
+
22
+ /** Cache TTL in milliseconds (60 seconds) */
23
+ const CACHE_TTL_MS = 60000;
24
+
25
+ /**
26
+ * L5 Squad Processor
27
+ *
28
+ * Discovers squads with .synapse/manifest files and merges their
29
+ * domain rules into the pipeline. Returns null if no squads found
30
+ * or squads/ directory is missing.
31
+ *
32
+ * @extends LayerProcessor
33
+ */
34
+ class L5SquadProcessor extends LayerProcessor {
35
+ constructor() {
36
+ super({ name: 'squad', layer: 5, timeout: 20 });
37
+ }
38
+
39
+ /**
40
+ * Discover squad domains and merge rules.
41
+ *
42
+ * Detection flow:
43
+ * 1. Resolve squads/ directory relative to synapsePath parent
44
+ * 2. Check cache (TTL 60s), use if fresh
45
+ * 3. Scan squads/ for .synapse/manifest files
46
+ * 4. Namespace domain keys: {SQUAD_NAME_UPPER}_{DOMAIN_KEY}
47
+ * 5. Read merge rules from {SQUAD}_EXTENDS key
48
+ * 6. Prioritize active squad if session.active_squad set
49
+ * 7. Load domain files and collect rules
50
+ *
51
+ * @param {object} context
52
+ * @param {string} context.prompt - Current prompt text
53
+ * @param {object} context.session - Session state (SYN-2 schema)
54
+ * @param {object} context.config - Config with synapsePath and manifest
55
+ * @param {object[]} context.previousLayers - Results from previous layers
56
+ * @returns {{ rules: string[], metadata: object } | null}
57
+ */
58
+ process(context) {
59
+ const { session, config } = context;
60
+ const { synapsePath } = config;
61
+
62
+ // 1. Resolve agents/ directory (sibling of .synapse/)
63
+ const projectRoot = path.dirname(synapsePath);
64
+ const squadsDir = path.join(projectRoot, 'agents');
65
+
66
+ // Graceful: missing squads/ directory
67
+ if (!fs.existsSync(squadsDir)) {
68
+ return null;
69
+ }
70
+
71
+ // 2. Check cache
72
+ const cacheDir = path.join(synapsePath, 'cache');
73
+ const cachePath = path.join(cacheDir, 'squad-manifests.json');
74
+ const cachedData = this._readCache(cachePath);
75
+
76
+ // 3. Discover squads (from cache or scan)
77
+ let squadManifests;
78
+ if (cachedData) {
79
+ squadManifests = cachedData.manifests;
80
+ } else {
81
+ squadManifests = this._scanSquads(squadsDir);
82
+ this._writeCache(cachePath, cacheDir, squadManifests);
83
+ }
84
+
85
+ if (Object.keys(squadManifests).length === 0) {
86
+ return null;
87
+ }
88
+
89
+ // 4. Determine active squad for prioritization
90
+ const activeSquadName = session.active_squad?.name || null;
91
+
92
+ // 5. Collect rules from all squads
93
+ const allRules = [];
94
+ const domainsLoaded = [];
95
+
96
+ // Process active squad first (priority)
97
+ const squadNames = Object.keys(squadManifests);
98
+ if (activeSquadName && squadManifests[activeSquadName]) {
99
+ this._loadSquadDomains(
100
+ activeSquadName, squadManifests[activeSquadName],
101
+ squadsDir, allRules, domainsLoaded,
102
+ );
103
+ }
104
+
105
+ // Process remaining squads
106
+ for (const squadName of squadNames) {
107
+ if (squadName === activeSquadName) continue;
108
+ this._loadSquadDomains(
109
+ squadName, squadManifests[squadName],
110
+ squadsDir, allRules, domainsLoaded,
111
+ );
112
+ }
113
+
114
+ if (allRules.length === 0) {
115
+ return null;
116
+ }
117
+
118
+ return {
119
+ rules: allRules,
120
+ metadata: {
121
+ layer: 5,
122
+ squadsFound: Object.keys(squadManifests).length,
123
+ domainsLoaded,
124
+ },
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Load domain rules from a single squad.
130
+ *
131
+ * @param {string} squadName - Squad directory name
132
+ * @param {object} manifest - Parsed squad manifest
133
+ * @param {string} squadsDir - Path to squads/ directory
134
+ * @param {string[]} allRules - Accumulator for rules
135
+ * @param {string[]} domainsLoaded - Accumulator for domain names
136
+ * @private
137
+ */
138
+ _loadSquadDomains(squadName, manifest, squadsDir, allRules, domainsLoaded) {
139
+ const squadUpper = squadName.toUpperCase();
140
+ const squadSynapsePath = path.join(squadsDir, squadName, '.synapse');
141
+
142
+ // Check merge mode from {SQUAD}_EXTENDS key
143
+ const extendsKey = `${squadUpper}_EXTENDS`;
144
+ const mergeMode = manifest.domains?.[extendsKey]?.file || 'extend';
145
+ const resolvedMerge = ['extend', 'override', 'none'].includes(mergeMode)
146
+ ? mergeMode : 'extend';
147
+
148
+ if (resolvedMerge === 'none') {
149
+ return; // Squad opted out of rule injection
150
+ }
151
+
152
+ for (const [domainKey, domain] of Object.entries(manifest.domains || {})) {
153
+ // Skip the EXTENDS meta-key
154
+ if (domainKey === extendsKey) continue;
155
+
156
+ const namespacedKey = `${squadUpper}_${domainKey}`;
157
+ const domainFile = domain.file
158
+ ? path.join(squadSynapsePath, domain.file)
159
+ : path.join(squadSynapsePath, domainKey.toLowerCase().replace(/_/g, '-'));
160
+
161
+ const rules = loadDomainFile(domainFile);
162
+ if (rules && rules.length > 0) {
163
+ allRules.push(...rules);
164
+ domainsLoaded.push(namespacedKey);
165
+ }
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Read cache file if it exists and is not stale.
171
+ *
172
+ * @param {string} cachePath - Path to cache JSON file
173
+ * @returns {object|null} Cached data or null if miss/stale/error
174
+ * @private
175
+ */
176
+ _readCache(cachePath) {
177
+ try {
178
+ const raw = fs.readFileSync(cachePath, 'utf8');
179
+ const cached = JSON.parse(raw);
180
+ if (cached.timestamp && (Date.now() - cached.timestamp) < CACHE_TTL_MS) {
181
+ return cached;
182
+ }
183
+ return null; // Stale
184
+ } catch (_error) {
185
+ return null; // Missing or corrupt
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Write cache file with current timestamp.
191
+ *
192
+ * @param {string} cachePath - Path to cache JSON file
193
+ * @param {string} cacheDir - Path to cache directory
194
+ * @param {object} manifests - Squad manifests to cache
195
+ * @private
196
+ */
197
+ _writeCache(cachePath, cacheDir, manifests) {
198
+ try {
199
+ if (!fs.existsSync(cacheDir)) {
200
+ fs.mkdirSync(cacheDir, { recursive: true });
201
+ }
202
+ fs.writeFileSync(cachePath, JSON.stringify({
203
+ timestamp: Date.now(),
204
+ manifests,
205
+ }));
206
+ } catch (_error) {
207
+ // Graceful: cache write failure is non-fatal
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Scan squads/ directory for .synapse/manifest files.
213
+ *
214
+ * @param {string} squadsDir - Path to squads/ directory
215
+ * @returns {object} Map of squadName -> parsed manifest
216
+ * @private
217
+ */
218
+ _scanSquads(squadsDir) {
219
+ const manifests = {};
220
+
221
+ let entries;
222
+ try {
223
+ entries = fs.readdirSync(squadsDir, { withFileTypes: true });
224
+ } catch (_error) {
225
+ return manifests;
226
+ }
227
+
228
+ for (const entry of entries) {
229
+ if (!entry.isDirectory()) continue;
230
+
231
+ const manifestPath = path.join(squadsDir, entry.name, '.synapse', 'manifest');
232
+ if (!fs.existsSync(manifestPath)) continue;
233
+
234
+ const parsed = parseManifest(manifestPath);
235
+ if (parsed && Object.keys(parsed.domains).length > 0) {
236
+ manifests[entry.name] = parsed;
237
+ }
238
+ }
239
+
240
+ return manifests;
241
+ }
242
+ }
243
+
244
+ module.exports = L5SquadProcessor;
@@ -0,0 +1,154 @@
1
+ /**
2
+ * L6 Keyword Layer Processor
3
+ *
4
+ * Scans manifest domains for recall keywords and matches them
5
+ * against the user prompt. Loads domain files for matching domains.
6
+ * Respects exclusion rules and deduplicates against previous layers.
7
+ *
8
+ * CARL parity: identical keyword matching behavior using domain-loader
9
+ * matchKeywords() and isExcluded() functions.
10
+ *
11
+ * @module core/synapse/layers/l6-keyword
12
+ * @version 1.0.0
13
+ * @created Story SYN-5 - Layer Processors L4-L7
14
+ */
15
+
16
+ const path = require('path');
17
+ const { loadDomainFile, matchKeywords, isExcluded } = require('../domain/domain-loader');
18
+ const LayerProcessor = require('./layer-processor');
19
+
20
+ /**
21
+ * L6 Keyword Processor
22
+ *
23
+ * Matches recall keywords in manifest domains against the prompt.
24
+ * Deduplicates domains already loaded by previous layers.
25
+ * Returns null if no keyword matches.
26
+ *
27
+ * @extends LayerProcessor
28
+ */
29
+ class L6KeywordProcessor extends LayerProcessor {
30
+ constructor() {
31
+ super({ name: 'keyword', layer: 6, timeout: 15 });
32
+ }
33
+
34
+ /**
35
+ * Match keywords and load corresponding domain rules.
36
+ *
37
+ * Detection flow:
38
+ * 1. Build set of already-loaded domains from previousLayers
39
+ * 2. Iterate manifest domains with recall keywords
40
+ * 3. Check exclusion (global + domain)
41
+ * 4. Check keyword match against prompt
42
+ * 5. Skip if domain already loaded by previous layer
43
+ * 6. Load domain file and collect rules
44
+ *
45
+ * @param {object} context
46
+ * @param {string} context.prompt - Current prompt text
47
+ * @param {object} context.session - Session state (SYN-2 schema)
48
+ * @param {object} context.config - Config with synapsePath and manifest
49
+ * @param {object[]} context.previousLayers - Results from previous layers
50
+ * @returns {{ rules: string[], metadata: object } | null}
51
+ */
52
+ process(context) {
53
+ const { prompt, config, previousLayers } = context;
54
+ const { manifest, synapsePath } = config;
55
+
56
+ if (!prompt) {
57
+ return null;
58
+ }
59
+
60
+ // 1. Build set of already-loaded domain sources
61
+ const loadedSources = this._extractLoadedSources(previousLayers || []);
62
+
63
+ const allRules = [];
64
+ const matchedDomains = [];
65
+ const skippedDuplicates = [];
66
+
67
+ const globalExclude = manifest.globalExclude || [];
68
+
69
+ // 2. Iterate domains with recall keywords
70
+ for (const [domainName, domain] of Object.entries(manifest.domains || {})) {
71
+ if (!domain.recall || domain.recall.length === 0) {
72
+ continue;
73
+ }
74
+
75
+ // 3. Check exclusion
76
+ if (isExcluded(prompt, globalExclude, domain.exclude || [])) {
77
+ continue;
78
+ }
79
+
80
+ // 4. Check keyword match
81
+ if (!matchKeywords(prompt, domain.recall)) {
82
+ continue;
83
+ }
84
+
85
+ // 5. Check dedup against previous layers
86
+ if (loadedSources.has(domainName)) {
87
+ skippedDuplicates.push(domainName);
88
+ continue;
89
+ }
90
+
91
+ // 6. Load domain file
92
+ const domainFile = domain.file
93
+ ? path.join(synapsePath, domain.file)
94
+ : path.join(synapsePath, domainName.toLowerCase().replace(/_/g, '-'));
95
+
96
+ const rules = loadDomainFile(domainFile);
97
+ if (rules && rules.length > 0) {
98
+ allRules.push(...rules);
99
+ matchedDomains.push(domainName);
100
+ }
101
+ }
102
+
103
+ if (allRules.length === 0) {
104
+ return null;
105
+ }
106
+
107
+ return {
108
+ rules: allRules,
109
+ metadata: {
110
+ layer: 6,
111
+ matchedDomains,
112
+ skippedDuplicates,
113
+ },
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Extract domain source identifiers from previous layer results.
119
+ *
120
+ * Looks at metadata.source and metadata.agentId from previous layers
121
+ * to build a set of domain names that are already loaded.
122
+ *
123
+ * @param {object[]} previousLayers - Array of { name, metadata } from prior layers
124
+ * @returns {Set<string>} Set of loaded domain identifiers
125
+ * @private
126
+ */
127
+ _extractLoadedSources(previousLayers) {
128
+ const sources = new Set();
129
+
130
+ for (const layer of previousLayers) {
131
+ if (!layer || !layer.metadata) continue;
132
+
133
+ // Extract source identifier from metadata
134
+ const source = layer.metadata.source;
135
+ if (source) {
136
+ sources.add(source);
137
+ // Also add the uppercase domain key form
138
+ const upperKey = source.toUpperCase().replace(/-/g, '_');
139
+ sources.add(upperKey);
140
+ }
141
+
142
+ // Track loaded domains from squad layer
143
+ if (layer.metadata.domainsLoaded) {
144
+ for (const d of layer.metadata.domainsLoaded) {
145
+ sources.add(d);
146
+ }
147
+ }
148
+ }
149
+
150
+ return sources;
151
+ }
152
+ }
153
+
154
+ module.exports = L6KeywordProcessor;