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,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;
|