@cleocode/adapters 2026.5.4 → 2026.5.6
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/package.json +4 -4
- package/dist/cant-context.js +0 -711
- package/dist/cant-context.js.map +0 -1
- package/dist/providers/claude-code/adapter.js +0 -222
- package/dist/providers/claude-code/adapter.js.map +0 -1
- package/dist/providers/claude-code/context-monitor.js +0 -159
- package/dist/providers/claude-code/context-monitor.js.map +0 -1
- package/dist/providers/claude-code/hooks.js +0 -428
- package/dist/providers/claude-code/hooks.js.map +0 -1
- package/dist/providers/claude-code/index.js +0 -41
- package/dist/providers/claude-code/index.js.map +0 -1
- package/dist/providers/claude-code/install.js +0 -310
- package/dist/providers/claude-code/install.js.map +0 -1
- package/dist/providers/claude-code/paths.js +0 -41
- package/dist/providers/claude-code/paths.js.map +0 -1
- package/dist/providers/claude-code/spawn.js +0 -198
- package/dist/providers/claude-code/spawn.js.map +0 -1
- package/dist/providers/claude-code/statusline.js +0 -130
- package/dist/providers/claude-code/statusline.js.map +0 -1
- package/dist/providers/claude-code/task-sync.js +0 -119
- package/dist/providers/claude-code/task-sync.js.map +0 -1
- package/dist/providers/claude-code/transport.js +0 -29
- package/dist/providers/claude-code/transport.js.map +0 -1
- package/dist/providers/claude-sdk/adapter.js +0 -88
- package/dist/providers/claude-sdk/adapter.js.map +0 -1
- package/dist/providers/claude-sdk/index.js +0 -34
- package/dist/providers/claude-sdk/index.js.map +0 -1
- package/dist/providers/claude-sdk/install.js +0 -61
- package/dist/providers/claude-sdk/install.js.map +0 -1
- package/dist/providers/claude-sdk/mcp-registry.js +0 -66
- package/dist/providers/claude-sdk/mcp-registry.js.map +0 -1
- package/dist/providers/claude-sdk/session-store.js +0 -84
- package/dist/providers/claude-sdk/session-store.js.map +0 -1
- package/dist/providers/claude-sdk/spawn.js +0 -251
- package/dist/providers/claude-sdk/spawn.js.map +0 -1
- package/dist/providers/claude-sdk/tool-bridge.js +0 -50
- package/dist/providers/claude-sdk/tool-bridge.js.map +0 -1
- package/dist/providers/codex/adapter.js +0 -146
- package/dist/providers/codex/adapter.js.map +0 -1
- package/dist/providers/codex/hooks.js +0 -113
- package/dist/providers/codex/hooks.js.map +0 -1
- package/dist/providers/codex/index.js +0 -40
- package/dist/providers/codex/index.js.map +0 -1
- package/dist/providers/codex/install.js +0 -132
- package/dist/providers/codex/install.js.map +0 -1
- package/dist/providers/codex/spawn.js +0 -203
- package/dist/providers/codex/spawn.js.map +0 -1
- package/dist/providers/cursor/adapter.js +0 -151
- package/dist/providers/cursor/adapter.js.map +0 -1
- package/dist/providers/cursor/hooks.js +0 -208
- package/dist/providers/cursor/hooks.js.map +0 -1
- package/dist/providers/cursor/index.js +0 -36
- package/dist/providers/cursor/index.js.map +0 -1
- package/dist/providers/cursor/install.js +0 -281
- package/dist/providers/cursor/install.js.map +0 -1
- package/dist/providers/cursor/spawn.js +0 -59
- package/dist/providers/cursor/spawn.js.map +0 -1
- package/dist/providers/gemini-cli/adapter.js +0 -158
- package/dist/providers/gemini-cli/adapter.js.map +0 -1
- package/dist/providers/gemini-cli/hooks.js +0 -128
- package/dist/providers/gemini-cli/hooks.js.map +0 -1
- package/dist/providers/gemini-cli/index.js +0 -40
- package/dist/providers/gemini-cli/index.js.map +0 -1
- package/dist/providers/gemini-cli/install.js +0 -124
- package/dist/providers/gemini-cli/install.js.map +0 -1
- package/dist/providers/gemini-cli/spawn.js +0 -195
- package/dist/providers/gemini-cli/spawn.js.map +0 -1
- package/dist/providers/kimi/adapter.js +0 -145
- package/dist/providers/kimi/adapter.js.map +0 -1
- package/dist/providers/kimi/hooks.js +0 -79
- package/dist/providers/kimi/hooks.js.map +0 -1
- package/dist/providers/kimi/index.js +0 -40
- package/dist/providers/kimi/index.js.map +0 -1
- package/dist/providers/kimi/install.js +0 -124
- package/dist/providers/kimi/install.js.map +0 -1
- package/dist/providers/kimi/spawn.js +0 -225
- package/dist/providers/kimi/spawn.js.map +0 -1
- package/dist/providers/openai-sdk/adapter.js +0 -121
- package/dist/providers/openai-sdk/adapter.js.map +0 -1
- package/dist/providers/openai-sdk/guardrails.js +0 -174
- package/dist/providers/openai-sdk/guardrails.js.map +0 -1
- package/dist/providers/openai-sdk/handoff.js +0 -128
- package/dist/providers/openai-sdk/handoff.js.map +0 -1
- package/dist/providers/openai-sdk/index.js +0 -40
- package/dist/providers/openai-sdk/index.js.map +0 -1
- package/dist/providers/openai-sdk/install.js +0 -120
- package/dist/providers/openai-sdk/install.js.map +0 -1
- package/dist/providers/openai-sdk/spawn.js +0 -361
- package/dist/providers/openai-sdk/spawn.js.map +0 -1
- package/dist/providers/openai-sdk/tracing.js +0 -159
- package/dist/providers/openai-sdk/tracing.js.map +0 -1
- package/dist/providers/opencode/adapter.js +0 -166
- package/dist/providers/opencode/adapter.js.map +0 -1
- package/dist/providers/opencode/hooks.js +0 -206
- package/dist/providers/opencode/hooks.js.map +0 -1
- package/dist/providers/opencode/index.js +0 -37
- package/dist/providers/opencode/index.js.map +0 -1
- package/dist/providers/opencode/install.js +0 -242
- package/dist/providers/opencode/install.js.map +0 -1
- package/dist/providers/opencode/spawn.js +0 -257
- package/dist/providers/opencode/spawn.js.map +0 -1
- package/dist/providers/pi/adapter.js +0 -220
- package/dist/providers/pi/adapter.js.map +0 -1
- package/dist/providers/pi/hooks.js +0 -223
- package/dist/providers/pi/hooks.js.map +0 -1
- package/dist/providers/pi/index.js +0 -38
- package/dist/providers/pi/index.js.map +0 -1
- package/dist/providers/pi/install.js +0 -183
- package/dist/providers/pi/install.js.map +0 -1
- package/dist/providers/pi/spawn.js +0 -187
- package/dist/providers/pi/spawn.js.map +0 -1
- package/dist/providers/shared/conduit-trace-writer.js +0 -65
- package/dist/providers/shared/conduit-trace-writer.js.map +0 -1
- package/dist/providers/shared/hook-template-installer.js +0 -209
- package/dist/providers/shared/hook-template-installer.js.map +0 -1
- package/dist/providers/shared/paths.js +0 -77
- package/dist/providers/shared/paths.js.map +0 -1
- package/dist/providers/shared/sdk-result-mapper.js +0 -55
- package/dist/providers/shared/sdk-result-mapper.js.map +0 -1
- package/dist/providers/shared/transcript-reader.js +0 -124
- package/dist/providers/shared/transcript-reader.js.map +0 -1
- package/dist/registry.js +0 -92
- package/dist/registry.js.map +0 -1
package/dist/cant-context.js
DELETED
|
@@ -1,711 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared CANT context builder for all spawn providers.
|
|
3
|
-
*
|
|
4
|
-
* Extracts the Pi bridge's CANT discovery/compile/inject logic into a reusable
|
|
5
|
-
* module that any spawn provider (Claude Code, OpenCode, Cursor, etc.) can call
|
|
6
|
-
* to enrich agent prompts with:
|
|
7
|
-
*
|
|
8
|
-
* 1. Compiled CANT bundle (team topology, agent personas, tool ACLs)
|
|
9
|
-
* 2. Memory bridge (recent decisions, handoff notes, key patterns)
|
|
10
|
-
* 3. Mental model injection (validate-on-load agent-specific observations)
|
|
11
|
-
* 4. NEXUS code intelligence context (callers, callees, impact data) [T625]
|
|
12
|
-
*
|
|
13
|
-
* All operations are best-effort: if any step fails (missing packages, empty
|
|
14
|
-
* directories, compilation errors), the base prompt is returned unchanged.
|
|
15
|
-
* This guarantees agents always spawn — CANT context is an enrichment, not a gate.
|
|
16
|
-
*
|
|
17
|
-
* Reference implementation: packages/cleo-os/extensions/cleo-cant-bridge.ts
|
|
18
|
-
* (Pi-only; this module generalizes the same logic for all providers)
|
|
19
|
-
*
|
|
20
|
-
* @task T555
|
|
21
|
-
* @task T625
|
|
22
|
-
*/
|
|
23
|
-
import { execFile } from 'node:child_process';
|
|
24
|
-
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
25
|
-
import { homedir } from 'node:os';
|
|
26
|
-
import { basename, join } from 'node:path';
|
|
27
|
-
import { promisify } from 'node:util';
|
|
28
|
-
const execFileAsync = promisify(execFile);
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
// Constants
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
/**
|
|
33
|
-
* Preamble text injected when an agent has mental model observations.
|
|
34
|
-
* The agent MUST re-evaluate each observation against current project state.
|
|
35
|
-
*/
|
|
36
|
-
const VALIDATE_ON_LOAD_PREAMBLE = '===== MENTAL MODEL (validate-on-load) =====\n' +
|
|
37
|
-
'These are your prior observations, patterns, and learnings for this project.\n' +
|
|
38
|
-
'Before acting, you MUST re-evaluate each entry against current project state.\n' +
|
|
39
|
-
'If an entry is stale, note it and proceed with fresh understanding.';
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
// Discovery functions (ported from cleo-cant-bridge.ts lines 418-526)
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
/**
|
|
44
|
-
* Recursively discover `.cant` files in a directory.
|
|
45
|
-
*
|
|
46
|
-
* @param dir - The directory to scan recursively.
|
|
47
|
-
* @returns An array of absolute paths to `.cant` files found.
|
|
48
|
-
*/
|
|
49
|
-
export function discoverCantFiles(dir) {
|
|
50
|
-
try {
|
|
51
|
-
const entries = readdirSync(dir, { recursive: true, withFileTypes: true });
|
|
52
|
-
const files = [];
|
|
53
|
-
for (const entry of entries) {
|
|
54
|
-
if (entry.isFile() && entry.name.endsWith('.cant')) {
|
|
55
|
-
const parent = entry.parentPath ?? dir;
|
|
56
|
-
files.push(join(parent, entry.name));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return files;
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
return [];
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Resolve XDG-compliant paths for the 3-tier CANT hierarchy.
|
|
67
|
-
*
|
|
68
|
-
* Respects `XDG_DATA_HOME` and `XDG_CONFIG_HOME` environment variables.
|
|
69
|
-
* Falls back to XDG defaults (`~/.local/share/` and `~/.config/`).
|
|
70
|
-
*
|
|
71
|
-
* @param projectDir - The project root directory (for the project tier).
|
|
72
|
-
* @returns An object with `global`, `user`, and `project` CANT directory paths.
|
|
73
|
-
*/
|
|
74
|
-
export function resolveThreeTierPaths(projectDir) {
|
|
75
|
-
const home = homedir();
|
|
76
|
-
const xdgData = process.env['XDG_DATA_HOME'] ?? join(home, '.local', 'share');
|
|
77
|
-
const xdgConfig = process.env['XDG_CONFIG_HOME'] ?? join(home, '.config');
|
|
78
|
-
return {
|
|
79
|
-
global: join(xdgData, 'cleo', 'cant'),
|
|
80
|
-
user: join(xdgConfig, 'cleo', 'cant'),
|
|
81
|
-
project: join(projectDir, '.cleo', 'cant'),
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Discover `.cant` files across all three tiers with override semantics.
|
|
86
|
-
*
|
|
87
|
-
* Scans global, user, and project tiers. Files in higher-precedence tiers
|
|
88
|
-
* override files in lower-precedence tiers that share the same basename.
|
|
89
|
-
* The precedence order is: project > user > global.
|
|
90
|
-
*
|
|
91
|
-
* @param projectDir - The project root directory.
|
|
92
|
-
* @returns An object containing the merged file list and per-tier statistics.
|
|
93
|
-
*/
|
|
94
|
-
export function discoverCantFilesMultiTier(projectDir) {
|
|
95
|
-
const paths = resolveThreeTierPaths(projectDir);
|
|
96
|
-
const globalFiles = discoverCantFiles(paths.global);
|
|
97
|
-
const userFiles = discoverCantFiles(paths.user);
|
|
98
|
-
const projectFiles = discoverCantFiles(paths.project);
|
|
99
|
-
// Build basename-keyed map; lowest precedence first so higher tiers override
|
|
100
|
-
const fileMap = new Map();
|
|
101
|
-
for (const file of globalFiles) {
|
|
102
|
-
fileMap.set(basename(file), file);
|
|
103
|
-
}
|
|
104
|
-
for (const file of userFiles) {
|
|
105
|
-
fileMap.set(basename(file), file);
|
|
106
|
-
}
|
|
107
|
-
for (const file of projectFiles) {
|
|
108
|
-
fileMap.set(basename(file), file);
|
|
109
|
-
}
|
|
110
|
-
const totalUniqueInputs = globalFiles.length + userFiles.length + projectFiles.length;
|
|
111
|
-
const overrides = totalUniqueInputs - fileMap.size;
|
|
112
|
-
return {
|
|
113
|
-
files: Array.from(fileMap.values()),
|
|
114
|
-
stats: {
|
|
115
|
-
global: globalFiles.length,
|
|
116
|
-
user: userFiles.length,
|
|
117
|
-
project: projectFiles.length,
|
|
118
|
-
overrides,
|
|
119
|
-
merged: fileMap.size,
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
// ---------------------------------------------------------------------------
|
|
124
|
-
// Memory bridge (ported from cleo-cant-bridge.ts lines 376-404)
|
|
125
|
-
// ---------------------------------------------------------------------------
|
|
126
|
-
/**
|
|
127
|
-
* Read the memory bridge file from a project's .cleo/ directory.
|
|
128
|
-
*
|
|
129
|
-
* @param projectDir - The project root directory.
|
|
130
|
-
* @returns The memory bridge content, or null if not found or empty.
|
|
131
|
-
*/
|
|
132
|
-
export function readMemoryBridge(projectDir) {
|
|
133
|
-
try {
|
|
134
|
-
const bridgePath = join(projectDir, '.cleo', 'memory-bridge.md');
|
|
135
|
-
if (!existsSync(bridgePath))
|
|
136
|
-
return null;
|
|
137
|
-
const content = readFileSync(bridgePath, 'utf-8');
|
|
138
|
-
return content.length > 0 ? content : null;
|
|
139
|
-
}
|
|
140
|
-
catch {
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Build the memory-bridge system-prompt block appended to every agent.
|
|
146
|
-
*
|
|
147
|
-
* Wraps the raw memory-bridge.md content in a clearly labeled section
|
|
148
|
-
* so the agent knows this is the CLEO project memory context.
|
|
149
|
-
*
|
|
150
|
-
* @param content - The raw memory-bridge.md content.
|
|
151
|
-
* @returns The formatted memory-bridge block for system prompt injection.
|
|
152
|
-
*/
|
|
153
|
-
export function buildMemoryBridgeBlock(content) {
|
|
154
|
-
return ('\n\n===== CLEO MEMORY BRIDGE =====\n' +
|
|
155
|
-
'This is your project memory context from .cleo/memory-bridge.md.\n' +
|
|
156
|
-
'Use it to understand recent decisions, handoff notes, and key patterns.\n\n' +
|
|
157
|
-
content.trim() +
|
|
158
|
-
'\n===== END MEMORY BRIDGE =====');
|
|
159
|
-
}
|
|
160
|
-
// ---------------------------------------------------------------------------
|
|
161
|
-
// Mental model injection (ported from cleo-cant-bridge.ts lines 113-135, 543-589)
|
|
162
|
-
// ---------------------------------------------------------------------------
|
|
163
|
-
/**
|
|
164
|
-
* Build the validate-on-load mental-model injection string.
|
|
165
|
-
*
|
|
166
|
-
* Pure function — no I/O, safe to call in tests without a real DB.
|
|
167
|
-
*
|
|
168
|
-
* @param agentName - Name of the spawned agent (used in the header line).
|
|
169
|
-
* @param observations - Prior mental-model observations to list.
|
|
170
|
-
* @returns System-prompt block with preamble and numbered observations,
|
|
171
|
-
* or empty string when `observations` is empty.
|
|
172
|
-
*/
|
|
173
|
-
export function buildMentalModelInjection(agentName, observations) {
|
|
174
|
-
if (observations.length === 0)
|
|
175
|
-
return '';
|
|
176
|
-
const lines = ['', `// Agent: ${agentName}`, VALIDATE_ON_LOAD_PREAMBLE, ''];
|
|
177
|
-
for (let i = 0; i < observations.length; i++) {
|
|
178
|
-
const obs = observations[i];
|
|
179
|
-
const datePart = obs.date ? ` [${obs.date}]` : '';
|
|
180
|
-
lines.push(`${i + 1}. [${obs.id}] (${obs.type})${datePart}: ${obs.title}`);
|
|
181
|
-
}
|
|
182
|
-
lines.push('===== END MENTAL MODEL =====');
|
|
183
|
-
return lines.join('\n');
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* Fetch mental model observations for an agent from brain.db.
|
|
187
|
-
*
|
|
188
|
-
* Uses dynamic import of `@cleocode/core` to avoid circular dependencies.
|
|
189
|
-
* Returns empty string on any failure (best-effort, never throws).
|
|
190
|
-
*
|
|
191
|
-
* @param agentName - The agent's name for scoped observation lookup.
|
|
192
|
-
* @param projectRoot - Project root directory for brain.db access.
|
|
193
|
-
* @returns The validate-on-load system-prompt block, or "" on failure/empty.
|
|
194
|
-
*/
|
|
195
|
-
async function fetchMentalModelInjection(agentName, projectRoot) {
|
|
196
|
-
try {
|
|
197
|
-
// Dynamic import — @cleocode/core is NOT a compile-time dependency of adapters.
|
|
198
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
199
|
-
const coreModule = (await import(/* webpackIgnore: true */ '@cleocode/core'));
|
|
200
|
-
if (typeof coreModule.memoryFind !== 'function')
|
|
201
|
-
return '';
|
|
202
|
-
const result = await coreModule.memoryFind({
|
|
203
|
-
query: agentName,
|
|
204
|
-
agent: agentName,
|
|
205
|
-
limit: 10,
|
|
206
|
-
tables: ['observations'],
|
|
207
|
-
}, projectRoot);
|
|
208
|
-
if (!result.success || !result.data?.results?.length)
|
|
209
|
-
return '';
|
|
210
|
-
return buildMentalModelInjection(agentName, result.data.results);
|
|
211
|
-
}
|
|
212
|
-
catch {
|
|
213
|
-
return '';
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
// ---------------------------------------------------------------------------
|
|
217
|
-
// Identity bootstrap (ported from cleo-cant-bridge.ts lines 858-964)
|
|
218
|
-
// ---------------------------------------------------------------------------
|
|
219
|
-
/**
|
|
220
|
-
* Read CLEOOS-IDENTITY.md from the project or global XDG location.
|
|
221
|
-
*
|
|
222
|
-
* Search order:
|
|
223
|
-
* 1. `<projectDir>/.cleo/CLEOOS-IDENTITY.md` (project-level, deployed by init)
|
|
224
|
-
* 2. `$XDG_DATA_HOME/cleo/CLEOOS-IDENTITY.md` (global XDG default)
|
|
225
|
-
*
|
|
226
|
-
* @param projectDir - The project root directory.
|
|
227
|
-
* @returns The identity file content, or null if not found.
|
|
228
|
-
*/
|
|
229
|
-
export function readIdentityFile(projectDir) {
|
|
230
|
-
const projectPath = join(projectDir, '.cleo', 'CLEOOS-IDENTITY.md');
|
|
231
|
-
if (existsSync(projectPath)) {
|
|
232
|
-
try {
|
|
233
|
-
const content = readFileSync(projectPath, 'utf-8');
|
|
234
|
-
return content.length > 0 ? content : null;
|
|
235
|
-
}
|
|
236
|
-
catch {
|
|
237
|
-
// Fall through to global path
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
const home = homedir();
|
|
241
|
-
const xdgData = process.env['XDG_DATA_HOME'] ?? join(home, '.local', 'share');
|
|
242
|
-
const globalPath = join(xdgData, 'cleo', 'CLEOOS-IDENTITY.md');
|
|
243
|
-
if (existsSync(globalPath)) {
|
|
244
|
-
try {
|
|
245
|
-
const content = readFileSync(globalPath, 'utf-8');
|
|
246
|
-
return content.length > 0 ? content : null;
|
|
247
|
-
}
|
|
248
|
-
catch {
|
|
249
|
-
return null;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
return null;
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Build the CleoOS identity bootstrap block for the main session agent.
|
|
256
|
-
*
|
|
257
|
-
* Reads CLEOOS-IDENTITY.md from disk and appends live operational context
|
|
258
|
-
* (current session briefing, next tasks, recent brain decisions) gathered
|
|
259
|
-
* via best-effort CLI calls with an 8-second timeout each.
|
|
260
|
-
*
|
|
261
|
-
* Returns an empty string on any failure — never throws.
|
|
262
|
-
*
|
|
263
|
-
* @param projectDir - The project root directory (used for CWD and file lookup).
|
|
264
|
-
* @returns The fully assembled identity block, or "" if unavailable.
|
|
265
|
-
*/
|
|
266
|
-
export async function buildIdentityBootstrap(projectDir) {
|
|
267
|
-
const identityContent = readIdentityFile(projectDir);
|
|
268
|
-
if (!identityContent)
|
|
269
|
-
return '';
|
|
270
|
-
// Gather live operational context in parallel (best-effort, 8s timeout each)
|
|
271
|
-
const [briefingResult, nextResult, memoryResult] = await Promise.allSettled([
|
|
272
|
-
execFileAsync('cleo', ['session', 'briefing', '--json'], {
|
|
273
|
-
timeout: 8_000,
|
|
274
|
-
cwd: projectDir || undefined,
|
|
275
|
-
}),
|
|
276
|
-
execFileAsync('cleo', ['next', '--json', '--limit', '3'], {
|
|
277
|
-
timeout: 8_000,
|
|
278
|
-
cwd: projectDir || undefined,
|
|
279
|
-
}),
|
|
280
|
-
execFileAsync('cleo', ['memory', 'find', 'decision', '--json', '--limit', '5'], {
|
|
281
|
-
timeout: 8_000,
|
|
282
|
-
cwd: projectDir || undefined,
|
|
283
|
-
}),
|
|
284
|
-
]);
|
|
285
|
-
const dynamicLines = [];
|
|
286
|
-
// Append briefing data if available
|
|
287
|
-
if (briefingResult.status === 'fulfilled') {
|
|
288
|
-
try {
|
|
289
|
-
const briefing = JSON.parse(briefingResult.value.stdout);
|
|
290
|
-
const data = briefing?.data ?? briefing;
|
|
291
|
-
if (data?.currentTask) {
|
|
292
|
-
dynamicLines.push('## Current Task');
|
|
293
|
-
dynamicLines.push(`- **${data.currentTask.id}**: ${data.currentTask.title} (${data.currentTask.status})`);
|
|
294
|
-
dynamicLines.push('');
|
|
295
|
-
}
|
|
296
|
-
if (data?.handoff?.note) {
|
|
297
|
-
dynamicLines.push('## Last Session Handoff');
|
|
298
|
-
dynamicLines.push(data.handoff.note);
|
|
299
|
-
dynamicLines.push('');
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
catch {
|
|
303
|
-
/* parse failure — skip briefing data */
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
// Append next tasks
|
|
307
|
-
if (nextResult.status === 'fulfilled') {
|
|
308
|
-
try {
|
|
309
|
-
const next = JSON.parse(nextResult.value.stdout);
|
|
310
|
-
const tasks = next?.data?.tasks ?? [];
|
|
311
|
-
if (Array.isArray(tasks) && tasks.length > 0) {
|
|
312
|
-
dynamicLines.push('## Next Tasks');
|
|
313
|
-
for (const t of tasks.slice(0, 3)) {
|
|
314
|
-
dynamicLines.push(`- **${t.id}**: ${t.title} (${t.priority ?? 'medium'})`);
|
|
315
|
-
}
|
|
316
|
-
dynamicLines.push('');
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
catch {
|
|
320
|
-
/* parse failure — skip */
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
// Append recent brain decisions
|
|
324
|
-
if (memoryResult.status === 'fulfilled') {
|
|
325
|
-
try {
|
|
326
|
-
const mem = JSON.parse(memoryResult.value.stdout);
|
|
327
|
-
const results = mem?.data?.results ?? [];
|
|
328
|
-
if (Array.isArray(results) && results.length > 0) {
|
|
329
|
-
dynamicLines.push('## Recent Brain Context');
|
|
330
|
-
for (const r of results.slice(0, 5)) {
|
|
331
|
-
dynamicLines.push(`- [${r.id}] ${r.title} (${r.date ?? ''})`);
|
|
332
|
-
}
|
|
333
|
-
dynamicLines.push('');
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
catch {
|
|
337
|
-
/* parse failure — skip */
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
const sections = [
|
|
341
|
-
'',
|
|
342
|
-
'===== CLEOOS IDENTITY BOOTSTRAP =====',
|
|
343
|
-
'',
|
|
344
|
-
identityContent.trim(),
|
|
345
|
-
];
|
|
346
|
-
if (dynamicLines.length > 0) {
|
|
347
|
-
sections.push('');
|
|
348
|
-
sections.push(...dynamicLines);
|
|
349
|
-
}
|
|
350
|
-
sections.push('===== END IDENTITY BOOTSTRAP =====');
|
|
351
|
-
return sections.join('\n');
|
|
352
|
-
}
|
|
353
|
-
// ---------------------------------------------------------------------------
|
|
354
|
-
// NEXUS context injection (T625)
|
|
355
|
-
// ---------------------------------------------------------------------------
|
|
356
|
-
/**
|
|
357
|
-
* Extract candidate symbol names from a task description or title.
|
|
358
|
-
*
|
|
359
|
-
* Uses a simple heuristic: camelCase, PascalCase, and snake_case tokens
|
|
360
|
-
* longer than 3 characters that look like code identifiers.
|
|
361
|
-
*
|
|
362
|
-
* Pure function — no I/O.
|
|
363
|
-
*
|
|
364
|
-
* @param text - Raw text to scan for symbol names (task title + description).
|
|
365
|
-
* @returns Deduplicated array of candidate symbol names, longest first.
|
|
366
|
-
*/
|
|
367
|
-
export function extractSymbolsFromText(text) {
|
|
368
|
-
// Match camelCase, PascalCase, snake_case identifiers of length >= 4
|
|
369
|
-
const identifierPattern = /\b([A-Z][a-zA-Z0-9]{3,}|[a-z][a-zA-Z0-9]{3,}[A-Z][a-zA-Z0-9]*|[a-z]{2,}_[a-z][a-zA-Z0-9_]*)\b/g;
|
|
370
|
-
const seen = new Set();
|
|
371
|
-
const matches = [];
|
|
372
|
-
for (const matchResult of text.matchAll(identifierPattern)) {
|
|
373
|
-
const symbol = matchResult[1];
|
|
374
|
-
if (symbol && !seen.has(symbol)) {
|
|
375
|
-
seen.add(symbol);
|
|
376
|
-
matches.push(symbol);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
// Sort longest first (more specific symbols first)
|
|
380
|
-
return matches.sort((a, b) => b.length - a.length).slice(0, 8);
|
|
381
|
-
}
|
|
382
|
-
/**
|
|
383
|
-
* Build a NEXUS code intelligence context block for a set of symbols.
|
|
384
|
-
*
|
|
385
|
-
* Queries `cleo nexus context <symbol> --json` for each symbol and
|
|
386
|
-
* returns a formatted prompt block with callers, callees, and impact data.
|
|
387
|
-
*
|
|
388
|
-
* All CLI calls are best-effort with a configurable timeout. Failures
|
|
389
|
-
* for individual symbols are silently skipped.
|
|
390
|
-
*
|
|
391
|
-
* @param options - Symbols, project dir, and optional limits.
|
|
392
|
-
* @returns Array of resolved NEXUS symbol contexts.
|
|
393
|
-
* @task T625
|
|
394
|
-
*/
|
|
395
|
-
export async function buildNexusContext(options) {
|
|
396
|
-
const { symbols, projectDir, limit = 10, timeoutMs = 10_000 } = options;
|
|
397
|
-
if (symbols.length === 0)
|
|
398
|
-
return [];
|
|
399
|
-
const results = [];
|
|
400
|
-
for (const symbol of symbols) {
|
|
401
|
-
try {
|
|
402
|
-
const { stdout } = await execFileAsync('cleo', ['nexus', 'context', symbol, '--json', '--limit', String(limit)], { timeout: timeoutMs, cwd: projectDir || undefined });
|
|
403
|
-
const parsed = JSON.parse(stdout);
|
|
404
|
-
if (!parsed.success || !parsed.data?.results?.length)
|
|
405
|
-
continue;
|
|
406
|
-
// Also fetch impact for risk classification
|
|
407
|
-
let riskLevel = 'UNKNOWN';
|
|
408
|
-
let totalImpacted = 0;
|
|
409
|
-
try {
|
|
410
|
-
const { stdout: impactStdout } = await execFileAsync('cleo', ['nexus', 'impact', symbol, '--json', '--depth', '2'], { timeout: timeoutMs, cwd: projectDir || undefined });
|
|
411
|
-
const impactParsed = JSON.parse(impactStdout);
|
|
412
|
-
if (impactParsed.success && impactParsed.data) {
|
|
413
|
-
riskLevel = String(impactParsed.data.riskLevel ?? 'UNKNOWN');
|
|
414
|
-
totalImpacted = Number(impactParsed.data.totalImpactedNodes ?? 0);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
catch {
|
|
418
|
-
// Impact fetch failure — non-fatal, use defaults
|
|
419
|
-
}
|
|
420
|
-
const primary = parsed.data.results[0];
|
|
421
|
-
if (!primary)
|
|
422
|
-
continue;
|
|
423
|
-
results.push({
|
|
424
|
-
name: String(primary.name ?? symbol),
|
|
425
|
-
kind: String(primary.kind ?? 'unknown'),
|
|
426
|
-
filePath: primary.filePath ? String(primary.filePath) : null,
|
|
427
|
-
callers: (primary.callers ?? []).slice(0, limit).map((c) => ({
|
|
428
|
-
name: String(c.name ?? ''),
|
|
429
|
-
kind: String(c.kind ?? 'unknown'),
|
|
430
|
-
filePath: c.filePath ? String(c.filePath) : null,
|
|
431
|
-
})),
|
|
432
|
-
callees: (primary.callees ?? []).slice(0, limit).map((c) => ({
|
|
433
|
-
name: String(c.name ?? ''),
|
|
434
|
-
kind: String(c.kind ?? 'unknown'),
|
|
435
|
-
filePath: c.filePath ? String(c.filePath) : null,
|
|
436
|
-
})),
|
|
437
|
-
riskLevel,
|
|
438
|
-
totalImpacted,
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
catch {
|
|
442
|
-
// Symbol not found or CLI unavailable — skip
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
return results;
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* Format a NEXUS context array into a system-prompt block.
|
|
449
|
-
*
|
|
450
|
-
* Pure function — no I/O, safe to call in tests without a real DB.
|
|
451
|
-
*
|
|
452
|
-
* @param contexts - Resolved NEXUS symbol contexts.
|
|
453
|
-
* @returns Formatted prompt block, or empty string when contexts is empty.
|
|
454
|
-
* @task T625
|
|
455
|
-
*/
|
|
456
|
-
export function buildNexusContextBlock(contexts) {
|
|
457
|
-
if (contexts.length === 0)
|
|
458
|
-
return '';
|
|
459
|
-
const lines = [
|
|
460
|
-
'',
|
|
461
|
-
'===== NEXUS CODE INTELLIGENCE (pre-modification context) =====',
|
|
462
|
-
'Consult this before modifying any of these symbols.',
|
|
463
|
-
'High-risk symbols MUST be checked for callers before editing.',
|
|
464
|
-
'',
|
|
465
|
-
];
|
|
466
|
-
for (const ctx of contexts) {
|
|
467
|
-
lines.push(`## ${ctx.name} [${ctx.kind}]${ctx.filePath ? ` ${ctx.filePath}` : ''}`);
|
|
468
|
-
lines.push(` Risk: ${ctx.riskLevel} | Impacted nodes: ${ctx.totalImpacted}`);
|
|
469
|
-
if (ctx.callers.length > 0) {
|
|
470
|
-
lines.push(` Callers (${ctx.callers.length}): ${ctx.callers.map((c) => c.name).join(', ')}`);
|
|
471
|
-
}
|
|
472
|
-
else {
|
|
473
|
-
lines.push(' Callers: none');
|
|
474
|
-
}
|
|
475
|
-
if (ctx.callees.length > 0) {
|
|
476
|
-
lines.push(` Callees (${ctx.callees.length}): ${ctx.callees.map((c) => c.name).join(', ')}`);
|
|
477
|
-
}
|
|
478
|
-
else {
|
|
479
|
-
lines.push(' Callees: none');
|
|
480
|
-
}
|
|
481
|
-
lines.push('');
|
|
482
|
-
}
|
|
483
|
-
lines.push('===== END NEXUS CONTEXT =====');
|
|
484
|
-
return lines.join('\n');
|
|
485
|
-
}
|
|
486
|
-
/**
|
|
487
|
-
* Inject NEXUS context for a task into the agent prompt.
|
|
488
|
-
*
|
|
489
|
-
* Fetches the task details from CLEO, extracts symbol names from the
|
|
490
|
-
* task title and description, then queries NEXUS for each symbol.
|
|
491
|
-
* Returns a formatted prompt block or empty string on any failure.
|
|
492
|
-
*
|
|
493
|
-
* @param taskId - CLEO task ID (e.g. "T625").
|
|
494
|
-
* @param projectDir - Project root directory.
|
|
495
|
-
* @returns Formatted NEXUS context block, or "" if unavailable.
|
|
496
|
-
* @task T625
|
|
497
|
-
*/
|
|
498
|
-
export async function buildNexusContextForTask(taskId, projectDir) {
|
|
499
|
-
try {
|
|
500
|
-
// Fetch task details to extract symbols
|
|
501
|
-
const { stdout: taskStdout } = await execFileAsync('cleo', ['show', taskId, '--json'], {
|
|
502
|
-
timeout: 8_000,
|
|
503
|
-
cwd: projectDir || undefined,
|
|
504
|
-
});
|
|
505
|
-
const taskData = JSON.parse(taskStdout);
|
|
506
|
-
const title = taskData?.data?.title ?? '';
|
|
507
|
-
const description = taskData?.data?.description ?? '';
|
|
508
|
-
const combinedText = `${title} ${description}`;
|
|
509
|
-
const symbols = extractSymbolsFromText(combinedText);
|
|
510
|
-
if (symbols.length === 0)
|
|
511
|
-
return '';
|
|
512
|
-
const contexts = await buildNexusContext({ symbols, projectDir });
|
|
513
|
-
return buildNexusContextBlock(contexts);
|
|
514
|
-
}
|
|
515
|
-
catch {
|
|
516
|
-
return '';
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
/**
|
|
520
|
-
* Run a post-modification NEXUS check on a set of changed files.
|
|
521
|
-
*
|
|
522
|
-
* Performs an incremental re-analysis of the changed files and compares
|
|
523
|
-
* relation counts before/after. Regressions (net loss of relations after
|
|
524
|
-
* modification) are flagged and returned for BRAIN storage.
|
|
525
|
-
*
|
|
526
|
-
* All operations are best-effort — never throws.
|
|
527
|
-
*
|
|
528
|
-
* @param changedFiles - Absolute paths to files that were modified.
|
|
529
|
-
* @param projectDir - Project root directory.
|
|
530
|
-
* @returns Check result with regression data.
|
|
531
|
-
* @task T625
|
|
532
|
-
*/
|
|
533
|
-
export async function runNexusPostModificationCheck(changedFiles, projectDir) {
|
|
534
|
-
const empty = {
|
|
535
|
-
success: false,
|
|
536
|
-
reindexedFiles: [],
|
|
537
|
-
newRelations: 0,
|
|
538
|
-
removedRelations: 0,
|
|
539
|
-
summary: 'NEXUS post-check unavailable',
|
|
540
|
-
regressions: [],
|
|
541
|
-
};
|
|
542
|
-
if (changedFiles.length === 0)
|
|
543
|
-
return empty;
|
|
544
|
-
try {
|
|
545
|
-
// 1. Snapshot current relation count before re-analysis
|
|
546
|
-
const { stdout: beforeStdout } = await execFileAsync('cleo', ['nexus', 'status', '--json'], {
|
|
547
|
-
timeout: 8_000,
|
|
548
|
-
cwd: projectDir || undefined,
|
|
549
|
-
});
|
|
550
|
-
let relationsBefore = 0;
|
|
551
|
-
try {
|
|
552
|
-
const beforeData = JSON.parse(beforeStdout);
|
|
553
|
-
relationsBefore = beforeData?.data?.relationCount ?? beforeData?.data?.relations ?? 0;
|
|
554
|
-
}
|
|
555
|
-
catch {
|
|
556
|
-
// Count unavailable — proceed anyway
|
|
557
|
-
}
|
|
558
|
-
// 2. Run incremental analysis on the project (covers changed files)
|
|
559
|
-
await execFileAsync('cleo', ['nexus', 'analyze', projectDir, '--incremental', '--json'], {
|
|
560
|
-
timeout: 60_000,
|
|
561
|
-
cwd: projectDir || undefined,
|
|
562
|
-
});
|
|
563
|
-
// 3. Snapshot relation count after re-analysis
|
|
564
|
-
const { stdout: afterStdout } = await execFileAsync('cleo', ['nexus', 'status', '--json'], {
|
|
565
|
-
timeout: 8_000,
|
|
566
|
-
cwd: projectDir || undefined,
|
|
567
|
-
});
|
|
568
|
-
let relationsAfter = 0;
|
|
569
|
-
try {
|
|
570
|
-
const afterData = JSON.parse(afterStdout);
|
|
571
|
-
relationsAfter = afterData?.data?.relationCount ?? afterData?.data?.relations ?? 0;
|
|
572
|
-
}
|
|
573
|
-
catch {
|
|
574
|
-
// Count unavailable
|
|
575
|
-
}
|
|
576
|
-
const delta = relationsAfter - relationsBefore;
|
|
577
|
-
const newRelations = Math.max(0, delta);
|
|
578
|
-
const removedRelations = Math.max(0, -delta);
|
|
579
|
-
const regressions = [];
|
|
580
|
-
// Flag significant relation loss as a potential regression
|
|
581
|
-
if (removedRelations > 5) {
|
|
582
|
-
regressions.push(`Lost ${removedRelations} relations after modifying: ${changedFiles.map((f) => f.split('/').pop() ?? f).join(', ')}`);
|
|
583
|
-
}
|
|
584
|
-
const summary = delta === 0
|
|
585
|
-
? `NEXUS stable — no relation changes after modifying ${changedFiles.length} file(s)`
|
|
586
|
-
: delta > 0
|
|
587
|
-
? `NEXUS: +${newRelations} new relations from ${changedFiles.length} file(s)`
|
|
588
|
-
: `NEXUS: -${removedRelations} relations removed from ${changedFiles.length} file(s)${regressions.length > 0 ? ' — REGRESSION DETECTED' : ''}`;
|
|
589
|
-
return {
|
|
590
|
-
success: true,
|
|
591
|
-
reindexedFiles: changedFiles,
|
|
592
|
-
newRelations,
|
|
593
|
-
removedRelations,
|
|
594
|
-
summary,
|
|
595
|
-
regressions,
|
|
596
|
-
};
|
|
597
|
-
}
|
|
598
|
-
catch {
|
|
599
|
-
return empty;
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
// ---------------------------------------------------------------------------
|
|
603
|
-
// Main entry point
|
|
604
|
-
// ---------------------------------------------------------------------------
|
|
605
|
-
/**
|
|
606
|
-
* Build an enriched prompt with CANT context, memory bridge, and mental model.
|
|
607
|
-
*
|
|
608
|
-
* This is the universal entry point for all spawn providers. It performs the
|
|
609
|
-
* same operations as the Pi bridge (cleo-cant-bridge.ts) but returns a string
|
|
610
|
-
* rather than hooking into Pi events:
|
|
611
|
-
*
|
|
612
|
-
* 1. Discovers `.cant` files across 3 tiers (global → user → project)
|
|
613
|
-
* 2. Compiles the CANT bundle via `@cleocode/cant`'s `compileBundle()`
|
|
614
|
-
* 3. Renders the compiled system prompt
|
|
615
|
-
* 4. Reads the memory bridge from `.cleo/memory-bridge.md`
|
|
616
|
-
* 5. Fetches mental model observations for the named agent
|
|
617
|
-
* 6b. Injects NEXUS code intelligence context for the task (T625)
|
|
618
|
-
* 7. Concatenates: basePrompt + CANT bundle + memory bridge + mental model + NEXUS
|
|
619
|
-
*
|
|
620
|
-
* All operations are best-effort. If any step fails, the base prompt is
|
|
621
|
-
* returned unchanged. CANT context is an enrichment, not a gate — agents
|
|
622
|
-
* always spawn regardless of CANT availability.
|
|
623
|
-
*
|
|
624
|
-
* @param options - Project dir, base prompt, and optional agent name.
|
|
625
|
-
* @returns The enriched prompt string, or basePrompt unchanged on failure.
|
|
626
|
-
*/
|
|
627
|
-
export async function buildCantEnrichedPrompt(options) {
|
|
628
|
-
const { projectDir, basePrompt, agentName, isMainAgent, compiledBundle, taskId } = options;
|
|
629
|
-
let appendix = '';
|
|
630
|
-
// Step 0: Identity bootstrap for the main session agent.
|
|
631
|
-
// Sub-agents receive identity via the CANT bundle + mental model below.
|
|
632
|
-
if (isMainAgent) {
|
|
633
|
-
try {
|
|
634
|
-
const identityBlock = await buildIdentityBootstrap(projectDir);
|
|
635
|
-
if (identityBlock) {
|
|
636
|
-
appendix += identityBlock;
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
catch {
|
|
640
|
-
// Identity bootstrap failure — non-fatal, continue enrichment
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
// Step 1-3: Discover and compile CANT bundle (or use pre-compiled bundle).
|
|
644
|
-
// When `compiledBundle` is provided (e.g. Pi session cache), skip
|
|
645
|
-
// discovery + compilation to avoid redundant work.
|
|
646
|
-
try {
|
|
647
|
-
if (compiledBundle) {
|
|
648
|
-
// Use pre-compiled bundle directly
|
|
649
|
-
appendix += `\n\n${compiledBundle}`;
|
|
650
|
-
}
|
|
651
|
-
else {
|
|
652
|
-
const { files } = discoverCantFilesMultiTier(projectDir);
|
|
653
|
-
if (files.length > 0) {
|
|
654
|
-
// Dynamic import — @cleocode/cant is NOT a compile-time dependency of adapters.
|
|
655
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
656
|
-
const cantModule = (await import(/* webpackIgnore: true */ '@cleocode/cant'));
|
|
657
|
-
if (typeof cantModule.compileBundle === 'function') {
|
|
658
|
-
const bundle = await cantModule.compileBundle(files);
|
|
659
|
-
if (bundle.valid) {
|
|
660
|
-
const rendered = bundle.renderSystemPrompt();
|
|
661
|
-
if (rendered) {
|
|
662
|
-
appendix += `\n\n${rendered}`;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
catch {
|
|
670
|
-
// CANT compilation failure — continue without bundle context
|
|
671
|
-
}
|
|
672
|
-
// Step 4: Append memory bridge
|
|
673
|
-
try {
|
|
674
|
-
const bridge = readMemoryBridge(projectDir);
|
|
675
|
-
if (bridge) {
|
|
676
|
-
appendix += buildMemoryBridgeBlock(bridge);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
catch {
|
|
680
|
-
// Memory bridge read failure — non-fatal
|
|
681
|
-
}
|
|
682
|
-
// Step 5: Append mental model for named agent
|
|
683
|
-
if (agentName) {
|
|
684
|
-
try {
|
|
685
|
-
const mentalModel = await fetchMentalModelInjection(agentName, projectDir);
|
|
686
|
-
if (mentalModel) {
|
|
687
|
-
appendix += `\n\n${mentalModel}`;
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
catch {
|
|
691
|
-
// Mental model fetch failure — non-fatal
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
// Step 6b: Inject NEXUS code intelligence context for the task (T625).
|
|
695
|
-
// Extracts symbols from the task title+description, queries NEXUS for
|
|
696
|
-
// callers/callees/impact data, and injects as a pre-modification guide.
|
|
697
|
-
if (taskId) {
|
|
698
|
-
try {
|
|
699
|
-
const nexusBlock = await buildNexusContextForTask(taskId, projectDir);
|
|
700
|
-
if (nexusBlock) {
|
|
701
|
-
appendix += nexusBlock;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
catch {
|
|
705
|
-
// NEXUS context fetch failure — non-fatal
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
// Step 7: Return enriched prompt (or unchanged basePrompt if no context found)
|
|
709
|
-
return appendix ? basePrompt + appendix : basePrompt;
|
|
710
|
-
}
|
|
711
|
-
//# sourceMappingURL=cant-context.js.map
|