sinapse-ai 1.6.1 → 1.8.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/.claude/CLAUDE.md +5 -11
- package/.claude/hooks/README.md +14 -1
- package/.claude/hooks/code-intel-pretool.cjs +115 -0
- package/.claude/hooks/enforce-delegation.cjs +31 -3
- package/.claude/hooks/enforce-framework-boundary.cjs +324 -0
- package/.claude/hooks/enforce-permission-mode.cjs +249 -0
- package/.claude/hooks/secret-scanning.cjs +34 -43
- package/.claude/hooks/synapse-engine.cjs +23 -23
- package/.claude/hooks/telemetry-post-tool.cjs +128 -0
- package/.claude/hooks/telemetry-stop.cjs +132 -0
- package/.claude/hooks/verify-packages.cjs +9 -2
- package/.claude/rules/documentation-first.md +1 -1
- package/.claude/rules/hook-governance.md +2 -0
- package/.sinapse-ai/cli/commands/health/index.js +24 -0
- package/.sinapse-ai/core/README.md +11 -0
- package/.sinapse-ai/core/config/config-loader.js +19 -0
- package/.sinapse-ai/core/config/merge-utils.js +8 -0
- package/.sinapse-ai/core/errors/constants.js +147 -0
- package/.sinapse-ai/core/errors/error-registry.js +176 -0
- package/.sinapse-ai/core/errors/index.js +50 -0
- package/.sinapse-ai/core/errors/serializer.js +147 -0
- package/.sinapse-ai/core/errors/sinapse-error.js +144 -0
- package/.sinapse-ai/core/errors/utils.js +187 -0
- package/.sinapse-ai/core/execution/build-orchestrator.js +47 -49
- package/.sinapse-ai/core/execution/build-state-manager.js +183 -31
- package/.sinapse-ai/core/execution/parallel-executor.js +7 -1
- package/.sinapse-ai/core/execution/semantic-merge-engine.js +26 -14
- package/.sinapse-ai/core/execution/subagent-dispatcher.js +201 -60
- package/.sinapse-ai/core/execution/wave-executor.js +4 -1
- package/.sinapse-ai/core/grounding/README.md +71 -11
- package/.sinapse-ai/core/health-check/checks/project/framework-config.js +38 -2
- package/.sinapse-ai/core/health-check/checks/project/package-json.js +47 -3
- package/.sinapse-ai/core/health-check/checks/services/gemini-cli.js +117 -0
- package/.sinapse-ai/core/health-check/checks/services/index.js +2 -0
- package/.sinapse-ai/core/health-check/healers/index.js +40 -3
- package/.sinapse-ai/core/ideation/ideation-engine.js +212 -107
- package/.sinapse-ai/core/ids/gate-evaluator.js +318 -0
- package/.sinapse-ai/core/ids/gates/g5-semantic-handshake.js +190 -0
- package/.sinapse-ai/core/ids/gates/g6-ci-integrity.js +162 -0
- package/.sinapse-ai/core/ids/index.js +30 -0
- package/.sinapse-ai/core/memory/__tests__/active-modules.verify.js +11 -0
- package/.sinapse-ai/core/memory/gotchas-memory.js +37 -2
- package/.sinapse-ai/core/orchestration/agent-invoker.js +29 -6
- package/.sinapse-ai/core/orchestration/brownfield-handler.js +36 -3
- package/.sinapse-ai/core/orchestration/condition-evaluator.js +57 -0
- package/.sinapse-ai/core/orchestration/executors/epic-3-executor.js +76 -5
- package/.sinapse-ai/core/orchestration/executors/epic-4-executor.js +63 -17
- package/.sinapse-ai/core/orchestration/executors/epic-6-executor.js +153 -41
- package/.sinapse-ai/core/orchestration/executors/epic-executor.js +40 -0
- package/.sinapse-ai/core/orchestration/greenfield-handler.js +87 -3
- package/.sinapse-ai/core/orchestration/master-orchestrator.js +150 -10
- package/.sinapse-ai/core/orchestration/parallel-executor.js +6 -1
- package/.sinapse-ai/core/orchestration/recovery-handler.js +81 -8
- package/.sinapse-ai/core/orchestration/workflow-executor.js +41 -0
- package/.sinapse-ai/core/registry/registry-loader.js +71 -5
- package/.sinapse-ai/core/registry/squad-agent-resolver.js +253 -0
- package/.sinapse-ai/core/synapse/context/context-tracker.js +104 -9
- package/.sinapse-ai/core/synapse/context/index.js +19 -0
- package/.sinapse-ai/core/synapse/context/semantic-handshake-engine.js +555 -0
- package/.sinapse-ai/core/synapse/diagnostics/collectors/pipeline-collector.js +4 -2
- package/.sinapse-ai/core/synapse/engine.js +43 -3
- package/.sinapse-ai/core/telemetry/ids-sink.js +188 -0
- package/.sinapse-ai/core/utils/output-formatter.js +8 -290
- package/.sinapse-ai/core/utils/spawn-safe.js +186 -0
- package/.sinapse-ai/core-config.yaml +68 -1
- package/.sinapse-ai/data/entity-registry.yaml +15082 -13618
- package/.sinapse-ai/data/registry-update-log.jsonl +143 -0
- package/.sinapse-ai/development/agents/developer.md +2 -0
- package/.sinapse-ai/development/agents/devops.md +9 -0
- package/.sinapse-ai/development/external-executors/README.md +18 -0
- package/.sinapse-ai/development/external-executors/codex.md +56 -0
- package/.sinapse-ai/development/scripts/populate-entity-registry.js +65 -9
- package/.sinapse-ai/development/scripts/squad/squad-downloader.js +169 -14
- package/.sinapse-ai/development/tasks/delegate-to-external-executor.md +152 -0
- package/.sinapse-ai/development/tasks/github-devops-pre-push-quality-gate.md +46 -29
- package/.sinapse-ai/development/tasks/update-sinapse.md +3 -3
- package/.sinapse-ai/hooks/sinapse-brand-grounding.cjs +4 -7
- package/.sinapse-ai/hooks/sinapse-ds-grounding.cjs +5 -8
- package/.sinapse-ai/hooks/sinapse-vault-grounding.cjs +6 -9
- package/.sinapse-ai/infrastructure/integrations/ai-providers/ai-provider-factory.js +4 -1
- package/.sinapse-ai/infrastructure/integrations/ai-providers/claude-provider.js +57 -55
- package/.sinapse-ai/infrastructure/integrations/pm-adapters/github-adapter.js +9 -7
- package/.sinapse-ai/infrastructure/scripts/ide-sync/gemini-commands.js +298 -0
- package/.sinapse-ai/infrastructure/scripts/ide-sync/index.js +127 -6
- package/.sinapse-ai/infrastructure/scripts/ide-sync/persona-renderer.js +97 -0
- package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/antigravity.js +121 -0
- package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/cursor.js +119 -0
- package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/github-copilot.js +191 -0
- package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/kimi.js +448 -0
- package/.sinapse-ai/install-manifest.yaml +218 -114
- package/.sinapse-ai/product/templates/engine/renderer.js +20 -1
- package/.sinapse-ai/scripts/pm.sh +18 -6
- package/bin/cli.js +17 -0
- package/bin/commands/agents.js +96 -0
- package/bin/commands/doctor.js +15 -0
- package/bin/commands/ideate.js +129 -0
- package/bin/commands/uninstall.js +40 -0
- package/bin/postinstall.js +50 -4
- package/bin/sinapse.js +146 -2
- package/bin/utils/secret-scanner-core.js +253 -0
- package/bin/utils/staged-secret-scan.js +106 -40
- package/docs/framework/collaboration-autonomy-plan.md +18 -18
- package/docs/guides/parallel-workflow.md +6 -6
- package/package.json +22 -5
- package/packages/installer/src/installer/git-hooks-installer.js +384 -0
- package/packages/installer/src/installer/sinapse-ai-installer.js +16 -0
- package/packages/installer/src/wizard/ide-config-generator.js +23 -0
- package/packages/installer/src/wizard/validators.js +38 -1
- package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +5 -1
- package/packages/installer/tests/unit/doctor/doctor-checks.test.js +44 -22
- package/packages/installer/tests/unit/git-hooks-installer.test.js +262 -0
- package/scripts/eval-runner.js +422 -0
- package/scripts/generate-install-manifest.js +13 -9
- package/scripts/generate-synapse-runtime.js +51 -0
- package/scripts/regenerate-orqx-stubs.ps1 +6 -5
- package/scripts/validate-all.js +1 -0
- package/scripts/validate-evals.js +466 -0
- package/scripts/validate-schemas.js +539 -0
- package/scripts/validate-squad-orqx.js +9 -2
- package/squads/claude-code-mastery/knowledge-base/memory-systems-reference.md +1 -1
- package/squads/squad-brand/templates/client-delivery-template.md +1 -1
- package/squads/squad-content/knowledge-base/social-compression-framework.md +1 -1
- package/squads/squad-council/knowledge-base/brand-strategy-models.md +1 -1
- package/.sinapse-ai/development/scripts/elicitation-engine.js +0 -385
- package/.sinapse-ai/development/scripts/elicitation-session-manager.js +0 -300
- package/.sinapse-ai/development/tasks/test-validation-task.md +0 -172
- package/docs/chrome-brain-upgrade-plan.md +0 -624
- package/docs/constitution-compliance.md +0 -87
- package/docs/mega-upgrade-orchestration-plan.md +0 -71
- package/docs/research-synthesis-for-upgrade.md +0 -511
- package/docs/security-audit-report.md +0 -306
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Squad Agent Resolver
|
|
3
|
+
*
|
|
4
|
+
* epic: orchestration-consolidation, F2 — makes the 177 squad personas (plus the
|
|
5
|
+
* framework agents) addressable BY CODE. Before this, the SubagentDispatcher knew
|
|
6
|
+
* only ~10 generic agents (@dev/@qa/...) and built a one-line "You are @x" prompt,
|
|
7
|
+
* discarding the real persona. This indexes every agent .md across `squads/` and
|
|
8
|
+
* the framework agent dirs, and loads the FULL persona on demand.
|
|
9
|
+
*
|
|
10
|
+
* Convention (verified across all squads): the agent id IS the file basename in
|
|
11
|
+
* kebab-case (e.g. `penetration-tester.md` -> id `penetration-tester`), which
|
|
12
|
+
* matches the embedded `id:` field. We key by basename for robustness and also
|
|
13
|
+
* register the embedded id / display name as aliases when present.
|
|
14
|
+
*
|
|
15
|
+
* @module core/registry/squad-agent-resolver
|
|
16
|
+
* @version 1.0.0
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
/** Directories scanned for agent definitions, relative to projectRoot. */
|
|
23
|
+
const AGENT_DIRS = [
|
|
24
|
+
'squads', // squads/<squad>/agents/<id>.md
|
|
25
|
+
path.join('.sinapse-ai', 'agents'),
|
|
26
|
+
path.join('.sinapse-ai', 'development', 'agents'),
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Short-form aliases used across the framework (dispatcher agentMapping, CLAUDE.md
|
|
31
|
+
* command table) → canonical file id. Lets '@dev' resolve to the developer persona
|
|
32
|
+
* instead of falling through to a one-line generic prompt.
|
|
33
|
+
*/
|
|
34
|
+
const ALIASES = {
|
|
35
|
+
dev: 'developer',
|
|
36
|
+
qa: 'quality-gate',
|
|
37
|
+
pm: 'project-lead',
|
|
38
|
+
po: 'product-lead',
|
|
39
|
+
sm: 'sprint-lead',
|
|
40
|
+
ux: 'ux-design-expert',
|
|
41
|
+
'ux-expert': 'ux-design-expert',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
class SquadAgentResolver {
|
|
45
|
+
/**
|
|
46
|
+
* @param {string} projectRoot - Project root directory
|
|
47
|
+
*/
|
|
48
|
+
constructor(projectRoot = process.cwd()) {
|
|
49
|
+
this.projectRoot = projectRoot;
|
|
50
|
+
this._index = null; // Map<normalizedKey, entry> — built lazily
|
|
51
|
+
this._personaCache = new Map(); // id -> persona content
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Normalize an agent reference for lookup: strip leading '@', lowercase,
|
|
56
|
+
* collapse separators. So '@penetration-tester', 'penetration_tester' and
|
|
57
|
+
* 'Penetration Tester' all resolve to the same key.
|
|
58
|
+
* @param {string} ref
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
61
|
+
static normalizeKey(ref) {
|
|
62
|
+
return String(ref || '')
|
|
63
|
+
.replace(/^@/, '')
|
|
64
|
+
.trim()
|
|
65
|
+
.toLowerCase()
|
|
66
|
+
.replace(/[\s_]+/g, '-');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Build (once) the index of every agent definition on disk.
|
|
71
|
+
* @returns {Map<string, Object>} normalizedKey -> { id, name, squad, filePath }
|
|
72
|
+
*/
|
|
73
|
+
buildIndex() {
|
|
74
|
+
if (this._index) return this._index;
|
|
75
|
+
const index = new Map();
|
|
76
|
+
|
|
77
|
+
const addEntry = (filePath, squad) => {
|
|
78
|
+
const base = path.basename(filePath, '.md');
|
|
79
|
+
if (!base || base.toUpperCase() === 'README') return;
|
|
80
|
+
const key = SquadAgentResolver.normalizeKey(base);
|
|
81
|
+
// First writer wins for a given key, but squad agents take precedence over
|
|
82
|
+
// framework duplicates only if a squad entry isn't already registered.
|
|
83
|
+
if (!index.has(key)) {
|
|
84
|
+
index.set(key, { id: base, name: base, squad, filePath });
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
for (const rel of AGENT_DIRS) {
|
|
89
|
+
const dir = path.join(this.projectRoot, rel);
|
|
90
|
+
if (!fs.existsSync(dir)) continue;
|
|
91
|
+
|
|
92
|
+
if (rel === 'squads') {
|
|
93
|
+
// squads/<squad>/agents/*.md
|
|
94
|
+
let squadDirs = [];
|
|
95
|
+
try {
|
|
96
|
+
squadDirs = fs.readdirSync(dir, { withFileTypes: true });
|
|
97
|
+
} catch {
|
|
98
|
+
squadDirs = [];
|
|
99
|
+
}
|
|
100
|
+
for (const sd of squadDirs) {
|
|
101
|
+
if (!sd.isDirectory()) continue;
|
|
102
|
+
const agentsDir = path.join(dir, sd.name, 'agents');
|
|
103
|
+
if (!fs.existsSync(agentsDir)) continue;
|
|
104
|
+
for (const f of this._listMd(agentsDir)) {
|
|
105
|
+
addEntry(path.join(agentsDir, f), sd.name);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
// flat dir of *.md
|
|
110
|
+
for (const f of this._listMd(dir)) {
|
|
111
|
+
addEntry(path.join(dir, f), null);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this._index = index;
|
|
117
|
+
return index;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* List .md files in a directory (non-recursive), tolerant of read errors.
|
|
122
|
+
* @param {string} dir
|
|
123
|
+
* @returns {string[]}
|
|
124
|
+
* @private
|
|
125
|
+
*/
|
|
126
|
+
_listMd(dir) {
|
|
127
|
+
try {
|
|
128
|
+
return fs.readdirSync(dir).filter((f) => f.toLowerCase().endsWith('.md'));
|
|
129
|
+
} catch {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Resolve an agent reference to its index entry.
|
|
136
|
+
* @param {string} ref - e.g. '@penetration-tester', 'dev', 'cloud-security-engineer'
|
|
137
|
+
* @returns {Object|null} { id, name, squad, filePath } or null when unknown
|
|
138
|
+
*/
|
|
139
|
+
resolve(ref) {
|
|
140
|
+
const index = this.buildIndex();
|
|
141
|
+
const key = SquadAgentResolver.normalizeKey(ref);
|
|
142
|
+
return index.get(key) || index.get(ALIASES[key]) || null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* True when the reference maps to a known agent definition on disk.
|
|
147
|
+
* @param {string} ref
|
|
148
|
+
* @returns {boolean}
|
|
149
|
+
*/
|
|
150
|
+
has(ref) {
|
|
151
|
+
return this.resolve(ref) !== null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Load the full persona markdown for an agent reference.
|
|
156
|
+
* @param {string} ref
|
|
157
|
+
* @returns {string|null} persona content, or null when unknown/unreadable
|
|
158
|
+
*/
|
|
159
|
+
loadPersona(ref) {
|
|
160
|
+
const entry = this.resolve(ref);
|
|
161
|
+
if (!entry) return null;
|
|
162
|
+
if (this._personaCache.has(entry.id)) return this._personaCache.get(entry.id);
|
|
163
|
+
let content = null;
|
|
164
|
+
try {
|
|
165
|
+
content = fs.readFileSync(entry.filePath, 'utf8');
|
|
166
|
+
} catch {
|
|
167
|
+
content = null;
|
|
168
|
+
}
|
|
169
|
+
this._personaCache.set(entry.id, content);
|
|
170
|
+
return content;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* All known agent ids (sorted), for diagnostics / `sinapse` listing.
|
|
175
|
+
* @returns {string[]}
|
|
176
|
+
*/
|
|
177
|
+
listIds() {
|
|
178
|
+
return [...this.buildIndex().values()].map((e) => e.id).sort();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Count of indexed agents.
|
|
183
|
+
* @returns {number}
|
|
184
|
+
*/
|
|
185
|
+
size() {
|
|
186
|
+
return this.buildIndex().size;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Normalized metadata for an agent, extracted from whatever structure the
|
|
191
|
+
* file uses (frontmatter, `# Agent: X`, `# X`, or an embedded ```yaml block).
|
|
192
|
+
* This is the uniform schema (SCHEMA-001 closure) without mutating the 199
|
|
193
|
+
* heterogeneous persona files — the canonical shape is DERIVED, not imposed.
|
|
194
|
+
*
|
|
195
|
+
* @param {string} ref
|
|
196
|
+
* @returns {{id, name, squad, type, hasStructuredYaml, file}|null}
|
|
197
|
+
*/
|
|
198
|
+
describe(ref) {
|
|
199
|
+
const entry = this.resolve(ref);
|
|
200
|
+
if (!entry) return null;
|
|
201
|
+
const content = this.loadPersona(entry.id) || '';
|
|
202
|
+
return {
|
|
203
|
+
id: entry.id,
|
|
204
|
+
name: SquadAgentResolver.extractName(content, entry.id),
|
|
205
|
+
squad: entry.squad || 'framework',
|
|
206
|
+
type: /-orqx$/.test(entry.id) ? 'orchestrator' : 'specialist',
|
|
207
|
+
hasStructuredYaml: /```ya?ml/.test(content) || /^\s*agent:\s*$/m.test(content),
|
|
208
|
+
file: entry.filePath,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Describe every indexed agent (sorted by id) — the uniform registry view.
|
|
214
|
+
* @returns {Array<Object>}
|
|
215
|
+
*/
|
|
216
|
+
list() {
|
|
217
|
+
return this.listIds().map((id) => this.describe(id));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Best-effort display name from heterogeneous structures. Falls back to a
|
|
222
|
+
* title-cased id so the result is never empty.
|
|
223
|
+
* @param {string} content - File content
|
|
224
|
+
* @param {string} id - Canonical id (filename)
|
|
225
|
+
* @returns {string}
|
|
226
|
+
*/
|
|
227
|
+
static extractName(content, id) {
|
|
228
|
+
const src = String(content || '');
|
|
229
|
+
// 1. YAML frontmatter `name:`
|
|
230
|
+
const fm = src.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
231
|
+
if (fm) {
|
|
232
|
+
const m = fm[1].match(/^\s*name:\s*["']?([^"'\n]+)["']?\s*$/m);
|
|
233
|
+
if (m && m[1].trim()) return m[1].trim();
|
|
234
|
+
}
|
|
235
|
+
// 2. Embedded yaml `name:` (inside an ```yaml block or agent: section)
|
|
236
|
+
const ym = src.match(/^\s*name:\s*["']?([^"'\n]+)["']?\s*$/m);
|
|
237
|
+
if (ym && ym[1].trim()) return ym[1].trim();
|
|
238
|
+
// 3. `# Agent: Name`
|
|
239
|
+
const ah = src.match(/^#\s*Agent:\s*(.+?)\s*$/m);
|
|
240
|
+
if (ah && ah[1].trim()) return ah[1].trim();
|
|
241
|
+
// 4. First `# Heading`
|
|
242
|
+
const h1 = src.match(/^#\s+(.+?)\s*$/m);
|
|
243
|
+
if (h1 && h1[1].trim()) return h1[1].trim().replace(/^@/, '');
|
|
244
|
+
// 5. Title-cased id
|
|
245
|
+
return String(id)
|
|
246
|
+
.split('-')
|
|
247
|
+
.map((w) => (w ? w[0].toUpperCase() + w.slice(1) : w))
|
|
248
|
+
.join(' ');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
module.exports = SquadAgentResolver;
|
|
253
|
+
module.exports.SquadAgentResolver = SquadAgentResolver;
|
|
@@ -5,13 +5,16 @@
|
|
|
5
5
|
* based on estimated token usage. Provides token budgets and layer filtering
|
|
6
6
|
* per bracket for the SynapseEngine orchestrator.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* Reads model context window from core-config.yaml → models.registry.
|
|
9
9
|
*
|
|
10
10
|
* @module core/synapse/context/context-tracker
|
|
11
|
-
* @version 1.
|
|
11
|
+
* @version 1.1.0
|
|
12
12
|
* @created Story SYN-3 - Context Bracket Tracker
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
15
18
|
/**
|
|
16
19
|
* Bracket definitions with thresholds and token budgets.
|
|
17
20
|
*
|
|
@@ -51,11 +54,84 @@ const XML_SAFETY_MULTIPLIER = 1.2;
|
|
|
51
54
|
|
|
52
55
|
/**
|
|
53
56
|
* Default configuration values.
|
|
57
|
+
* maxContext is the fallback when core-config.yaml is unavailable.
|
|
54
58
|
*/
|
|
55
|
-
const DEFAULTS = {
|
|
59
|
+
const DEFAULTS = Object.freeze({
|
|
56
60
|
avgTokensPerPrompt: 1500,
|
|
57
61
|
maxContext: 200000,
|
|
58
|
-
};
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/** Cache for model config by project root (read once per root per process). */
|
|
65
|
+
const _modelConfigCache = new Map();
|
|
66
|
+
|
|
67
|
+
function cloneModelConfig(config) {
|
|
68
|
+
return { ...config };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function cacheModelConfig(root, config) {
|
|
72
|
+
const cachedConfig = Object.freeze(cloneModelConfig(config));
|
|
73
|
+
_modelConfigCache.set(root, cachedConfig);
|
|
74
|
+
return cloneModelConfig(cachedConfig);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isPositiveFiniteNumber(value) {
|
|
78
|
+
return Number.isFinite(value) && value > 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Resolve the project root used for model config lookup.
|
|
83
|
+
*
|
|
84
|
+
* @param {string|null} basePath - Optional project root override
|
|
85
|
+
* @returns {string}
|
|
86
|
+
*/
|
|
87
|
+
function resolveConfigRoot(basePath) {
|
|
88
|
+
return path.resolve(basePath || path.resolve(__dirname, '..', '..', '..', '..'));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Read model configuration from core-config.yaml → models section.
|
|
93
|
+
* Returns { contextWindow, avgTokensPerPrompt } for the active model.
|
|
94
|
+
* Falls back to DEFAULTS if config is missing or malformed.
|
|
95
|
+
*
|
|
96
|
+
* @param {string|null} [basePath=null] - Project root override (defaults to __dirname-based resolution)
|
|
97
|
+
* @returns {{ maxContext: number, avgTokensPerPrompt: number }}
|
|
98
|
+
*/
|
|
99
|
+
function getModelConfig(basePath = null) {
|
|
100
|
+
const root = resolveConfigRoot(basePath);
|
|
101
|
+
if (_modelConfigCache.has(root)) return cloneModelConfig(_modelConfigCache.get(root));
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const yaml = require('js-yaml');
|
|
105
|
+
const configPath = path.join(root, '.sinapse-ai', 'core-config.yaml');
|
|
106
|
+
if (!fs.existsSync(configPath)) {
|
|
107
|
+
return cacheModelConfig(root, DEFAULTS);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const config = yaml.load(fs.readFileSync(configPath, 'utf8'));
|
|
111
|
+
const models = config && config.models;
|
|
112
|
+
if (!models || !models.registry || !models.active) {
|
|
113
|
+
return cacheModelConfig(root, DEFAULTS);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const activeModel = models.registry[models.active];
|
|
117
|
+
if (!activeModel || !isPositiveFiniteNumber(activeModel.contextWindow)) {
|
|
118
|
+
return cacheModelConfig(root, DEFAULTS);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const modelConfig = {
|
|
122
|
+
maxContext: activeModel.contextWindow,
|
|
123
|
+
avgTokensPerPrompt: isPositiveFiniteNumber(activeModel.avgTokensPerPrompt)
|
|
124
|
+
? activeModel.avgTokensPerPrompt
|
|
125
|
+
: DEFAULTS.avgTokensPerPrompt,
|
|
126
|
+
};
|
|
127
|
+
return cacheModelConfig(root, modelConfig);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
if (process.env.DEBUG || process.env.SINAPSE_DEBUG) {
|
|
130
|
+
console.warn('[context-tracker] Failed to load model config, using defaults:', err.message);
|
|
131
|
+
}
|
|
132
|
+
return cacheModelConfig(root, DEFAULTS);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
59
135
|
|
|
60
136
|
/**
|
|
61
137
|
* Layer configurations per bracket.
|
|
@@ -101,16 +177,20 @@ function calculateBracket(contextPercent) {
|
|
|
101
177
|
* Formula: 100 - ((promptCount * avgTokensPerPrompt) / maxContext * 100)
|
|
102
178
|
* Result is clamped to 0-100 range.
|
|
103
179
|
*
|
|
180
|
+
* Reads maxContext and avgTokensPerPrompt from core-config.yaml → models.registry
|
|
181
|
+
* for the active model. Options parameter can override for testing.
|
|
182
|
+
*
|
|
104
183
|
* @param {number} promptCount - Number of prompts in current session
|
|
105
|
-
* @param {Object} [options={}] - Configuration options
|
|
106
|
-
* @param {number} [options.avgTokensPerPrompt
|
|
107
|
-
* @param {number} [options.maxContext
|
|
184
|
+
* @param {Object} [options={}] - Configuration options (override config values)
|
|
185
|
+
* @param {number} [options.avgTokensPerPrompt] - Average tokens per prompt
|
|
186
|
+
* @param {number} [options.maxContext] - Maximum context window size in tokens
|
|
108
187
|
* @returns {number} Percentage of context remaining (0.0 to 100.0)
|
|
109
188
|
*/
|
|
110
189
|
function estimateContextPercent(promptCount, options = {}) {
|
|
190
|
+
const modelConfig = getModelConfig();
|
|
111
191
|
const {
|
|
112
|
-
avgTokensPerPrompt =
|
|
113
|
-
maxContext =
|
|
192
|
+
avgTokensPerPrompt = modelConfig.avgTokensPerPrompt,
|
|
193
|
+
maxContext = modelConfig.maxContext,
|
|
114
194
|
} = options;
|
|
115
195
|
|
|
116
196
|
if (typeof promptCount !== 'number' || isNaN(promptCount) || promptCount < 0) {
|
|
@@ -184,6 +264,19 @@ function needsMemoryHints(bracket) {
|
|
|
184
264
|
return bracket === 'DEPLETED' || bracket === 'CRITICAL';
|
|
185
265
|
}
|
|
186
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Reset the model config cache. Useful for tests or after config changes.
|
|
269
|
+
*
|
|
270
|
+
* @param {string|null} [basePath=null] - Optional project root override
|
|
271
|
+
*/
|
|
272
|
+
function resetModelConfigCache(basePath = null) {
|
|
273
|
+
if (basePath === null) {
|
|
274
|
+
_modelConfigCache.clear();
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
_modelConfigCache.delete(resolveConfigRoot(basePath));
|
|
278
|
+
}
|
|
279
|
+
|
|
187
280
|
module.exports = {
|
|
188
281
|
calculateBracket,
|
|
189
282
|
estimateContextPercent,
|
|
@@ -191,6 +284,8 @@ module.exports = {
|
|
|
191
284
|
getActiveLayers,
|
|
192
285
|
needsHandoffWarning,
|
|
193
286
|
needsMemoryHints,
|
|
287
|
+
getModelConfig,
|
|
288
|
+
resetModelConfigCache,
|
|
194
289
|
BRACKETS,
|
|
195
290
|
TOKEN_BUDGETS,
|
|
196
291
|
DEFAULTS,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SYNAPSE context runtime exports.
|
|
3
|
+
*
|
|
4
|
+
* @module core/synapse/context
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const contextTracker = require(path.resolve(__dirname, './context-tracker.js'));
|
|
12
|
+
const contextBuilder = require(path.resolve(__dirname, './context-builder.js'));
|
|
13
|
+
const semanticHandshake = require(path.resolve(__dirname, './semantic-handshake-engine.js'));
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
...contextTracker,
|
|
17
|
+
...contextBuilder,
|
|
18
|
+
...semanticHandshake,
|
|
19
|
+
};
|