@compr/opscontext-mcp 2.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/CHANGELOG.md +313 -0
- package/LICENSE +83 -0
- package/README.md +470 -0
- package/defaults/learnings.json +146 -0
- package/dist/activation.d.ts +48 -0
- package/dist/activation.js +377 -0
- package/dist/adapters.d.ts +101 -0
- package/dist/adapters.js +171 -0
- package/dist/agents.d.ts +137 -0
- package/dist/agents.js +1638 -0
- package/dist/audit.d.ts +23 -0
- package/dist/audit.js +163 -0
- package/dist/cache.d.ts +15 -0
- package/dist/cache.js +117 -0
- package/dist/claude-integration.d.ts +95 -0
- package/dist/claude-integration.js +247 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.js +1823 -0
- package/dist/code-chunker.d.ts +12 -0
- package/dist/code-chunker.js +270 -0
- package/dist/collectors.d.ts +63 -0
- package/dist/collectors.js +617 -0
- package/dist/config.d.ts +73 -0
- package/dist/config.js +239 -0
- package/dist/embeddings.d.ts +36 -0
- package/dist/embeddings.js +124 -0
- package/dist/firewall.d.ts +133 -0
- package/dist/firewall.js +631 -0
- package/dist/hooks.d.ts +76 -0
- package/dist/hooks.js +313 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1081 -0
- package/dist/ingest.d.ts +32 -0
- package/dist/ingest.js +162 -0
- package/dist/learnings.d.ts +108 -0
- package/dist/learnings.js +714 -0
- package/dist/license-sig.d.ts +47 -0
- package/dist/license-sig.js +104 -0
- package/dist/policy.d.ts +131 -0
- package/dist/policy.js +182 -0
- package/dist/search.d.ts +11 -0
- package/dist/search.js +99 -0
- package/dist/sessions.d.ts +46 -0
- package/dist/sessions.js +153 -0
- package/examples/adapters/notion-adapter.js +108 -0
- package/examples/adapters/rss-adapter.js +76 -0
- package/package.json +87 -0
- package/skills/opscontext/SKILL.md +260 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { resolve, join } from "path";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { readFileSync, existsSync, readdirSync, statSync } from "fs";
|
|
4
|
+
import { discoverClaudeMemory } from "./claude-integration.js";
|
|
5
|
+
const DEFAULT_PATTERNS = [
|
|
6
|
+
// GitHub Copilot
|
|
7
|
+
".github/copilot-instructions.md",
|
|
8
|
+
".github/instructions/copilot-instructions.md",
|
|
9
|
+
".github/SKILLS.md",
|
|
10
|
+
// Claude Code
|
|
11
|
+
"CLAUDE.md",
|
|
12
|
+
// Cursor
|
|
13
|
+
".cursorrules",
|
|
14
|
+
".cursor/rules",
|
|
15
|
+
// Codex / multi-agent
|
|
16
|
+
"AGENTS.md",
|
|
17
|
+
// Context engineering
|
|
18
|
+
"CONTEXT_MAP.md",
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Look for contextengine.json in standard locations.
|
|
22
|
+
* Priority: env var > CWD > home dir
|
|
23
|
+
*/
|
|
24
|
+
function findConfigFile() {
|
|
25
|
+
const candidates = [];
|
|
26
|
+
const envPath = process.env.CONTEXTENGINE_CONFIG;
|
|
27
|
+
if (envPath) {
|
|
28
|
+
candidates.push(resolve(envPath));
|
|
29
|
+
}
|
|
30
|
+
candidates.push(resolve(process.cwd(), "contextengine.json"), resolve(homedir(), ".contextengine.json"));
|
|
31
|
+
for (const c of candidates) {
|
|
32
|
+
if (existsSync(c))
|
|
33
|
+
return c;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Auto-discover knowledge files by scanning directories for known patterns.
|
|
39
|
+
* Scans one level deep (each subdirectory = a project).
|
|
40
|
+
*/
|
|
41
|
+
function discoverSources(dirs, patterns) {
|
|
42
|
+
const sources = [];
|
|
43
|
+
for (const dir of dirs) {
|
|
44
|
+
const absDir = resolve(dir.replace(/^~/, homedir()));
|
|
45
|
+
if (!existsSync(absDir))
|
|
46
|
+
continue;
|
|
47
|
+
// Check patterns at this level
|
|
48
|
+
for (const pattern of patterns) {
|
|
49
|
+
const filePath = join(absDir, pattern);
|
|
50
|
+
if (existsSync(filePath)) {
|
|
51
|
+
const dirName = absDir.split("/").pop() || absDir;
|
|
52
|
+
const fileName = pattern.split("/").pop() || pattern;
|
|
53
|
+
sources.push({
|
|
54
|
+
name: `${dirName} — ${fileName}`,
|
|
55
|
+
path: filePath,
|
|
56
|
+
type: "markdown",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Scan one level deep (subdirectories = projects)
|
|
61
|
+
try {
|
|
62
|
+
for (const entry of readdirSync(absDir)) {
|
|
63
|
+
if (entry.startsWith(".") || entry === "node_modules")
|
|
64
|
+
continue;
|
|
65
|
+
const subDir = join(absDir, entry);
|
|
66
|
+
try {
|
|
67
|
+
if (!statSync(subDir).isDirectory())
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
for (const pattern of patterns) {
|
|
74
|
+
const filePath = join(subDir, pattern);
|
|
75
|
+
if (existsSync(filePath)) {
|
|
76
|
+
const fileName = pattern.split("/").pop() || pattern;
|
|
77
|
+
sources.push({
|
|
78
|
+
name: `${entry} — ${fileName}`,
|
|
79
|
+
path: filePath,
|
|
80
|
+
type: "markdown",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Permission denied — skip
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return sources;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Load knowledge sources.
|
|
94
|
+
*
|
|
95
|
+
* Resolution order:
|
|
96
|
+
* 1. Config file (CONTEXTENGINE_CONFIG env, ./contextengine.json, ~/.contextengine.json)
|
|
97
|
+
* 2. CONTEXTENGINE_WORKSPACES env var (colon-separated paths)
|
|
98
|
+
* 3. Auto-discover from ~/Projects
|
|
99
|
+
*
|
|
100
|
+
* Claude Code auto-memory (~/.claude/projects/<slug>/memory/*.md) is added
|
|
101
|
+
* to every resolution path unless OPSCONTEXT_SKIP_CLAUDE_MEMORY=1.
|
|
102
|
+
*/
|
|
103
|
+
export function loadSources() {
|
|
104
|
+
const configPath = findConfigFile();
|
|
105
|
+
if (configPath) {
|
|
106
|
+
console.error(`[ContextEngine] 📄 Config: ${configPath}`);
|
|
107
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
108
|
+
const sources = [];
|
|
109
|
+
// Explicit sources
|
|
110
|
+
if (config.sources) {
|
|
111
|
+
for (const s of config.sources) {
|
|
112
|
+
const absPath = resolve(configPath, "..", s.path.replace(/^~/, homedir()));
|
|
113
|
+
if (existsSync(absPath)) {
|
|
114
|
+
sources.push({ name: s.name, path: absPath, type: "markdown" });
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
console.error(`[ContextEngine] ⚠ Not found: ${absPath}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Workspace auto-discovery
|
|
122
|
+
if (config.workspaces) {
|
|
123
|
+
const patterns = config.patterns || DEFAULT_PATTERNS;
|
|
124
|
+
const resolved = config.workspaces.map((w) => resolve(configPath, "..", w.replace(/^~/, homedir())));
|
|
125
|
+
sources.push(...discoverSources(resolved, patterns));
|
|
126
|
+
}
|
|
127
|
+
sources.push(...claudeMemorySources());
|
|
128
|
+
return dedup(sources);
|
|
129
|
+
}
|
|
130
|
+
// Env var fallback
|
|
131
|
+
const envWorkspaces = process.env.CONTEXTENGINE_WORKSPACES;
|
|
132
|
+
if (envWorkspaces) {
|
|
133
|
+
console.error(`[ContextEngine] 🔍 Discovering from CONTEXTENGINE_WORKSPACES`);
|
|
134
|
+
const dirs = envWorkspaces.split(":").filter(Boolean);
|
|
135
|
+
const found = discoverSources(dirs, DEFAULT_PATTERNS);
|
|
136
|
+
return dedup([...found, ...claudeMemorySources()]);
|
|
137
|
+
}
|
|
138
|
+
// Auto-discover from ~/Projects
|
|
139
|
+
const projectsDir = resolve(homedir(), "Projects");
|
|
140
|
+
if (existsSync(projectsDir)) {
|
|
141
|
+
console.error(`[ContextEngine] 🔍 Auto-discovering from ~/Projects`);
|
|
142
|
+
const found = discoverSources([projectsDir], DEFAULT_PATTERNS);
|
|
143
|
+
return dedup([...found, ...claudeMemorySources()]);
|
|
144
|
+
}
|
|
145
|
+
console.error(`[ContextEngine] ⚠ No sources found. Create contextengine.json or set CONTEXTENGINE_WORKSPACES.`);
|
|
146
|
+
// Claude memory may still exist even without project workspaces — surface it.
|
|
147
|
+
return claudeMemorySources();
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Pull Claude Code auto-memory into the source list (read-only). Skips when
|
|
151
|
+
* OPSCONTEXT_SKIP_CLAUDE_MEMORY=1 (escape hatch for tests + air-gapped runs
|
|
152
|
+
* where ~/.claude/ contents may not be indexable).
|
|
153
|
+
*/
|
|
154
|
+
function claudeMemorySources() {
|
|
155
|
+
if (process.env.OPSCONTEXT_SKIP_CLAUDE_MEMORY === "1")
|
|
156
|
+
return [];
|
|
157
|
+
try {
|
|
158
|
+
return discoverClaudeMemory();
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** Remove duplicate paths */
|
|
165
|
+
function dedup(sources) {
|
|
166
|
+
const seen = new Set();
|
|
167
|
+
return sources.filter((s) => {
|
|
168
|
+
if (seen.has(s.path))
|
|
169
|
+
return false;
|
|
170
|
+
seen.add(s.path);
|
|
171
|
+
return true;
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Discover project directories from workspaces.
|
|
176
|
+
* Returns one entry per top-level project found.
|
|
177
|
+
*/
|
|
178
|
+
export function loadProjectDirs() {
|
|
179
|
+
const configPath = findConfigFile();
|
|
180
|
+
const dirs = [];
|
|
181
|
+
let workspaceDirs = [];
|
|
182
|
+
if (configPath) {
|
|
183
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
184
|
+
if (config.collectOps === false)
|
|
185
|
+
return []; // opted out
|
|
186
|
+
if (config.workspaces) {
|
|
187
|
+
workspaceDirs = config.workspaces.map((w) => resolve(configPath, "..", w.replace(/^~/, homedir())));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Env var fallback
|
|
191
|
+
if (workspaceDirs.length === 0) {
|
|
192
|
+
const envWorkspaces = process.env.CONTEXTENGINE_WORKSPACES;
|
|
193
|
+
if (envWorkspaces) {
|
|
194
|
+
workspaceDirs = envWorkspaces.split(":").filter(Boolean);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Auto-discover fallback
|
|
198
|
+
if (workspaceDirs.length === 0) {
|
|
199
|
+
const projectsDir = resolve(homedir(), "Projects");
|
|
200
|
+
if (existsSync(projectsDir)) {
|
|
201
|
+
workspaceDirs = [projectsDir];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
for (const wsDir of workspaceDirs) {
|
|
205
|
+
const absDir = resolve(wsDir.replace(/^~/, homedir()));
|
|
206
|
+
if (!existsSync(absDir))
|
|
207
|
+
continue;
|
|
208
|
+
try {
|
|
209
|
+
for (const entry of readdirSync(absDir)) {
|
|
210
|
+
if (entry.startsWith(".") || entry === "node_modules")
|
|
211
|
+
continue;
|
|
212
|
+
const subDir = join(absDir, entry);
|
|
213
|
+
try {
|
|
214
|
+
if (!statSync(subDir).isDirectory())
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
dirs.push({ name: entry, path: subDir });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
// Permission denied — skip
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return dirs;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Load the raw config (for checking flags like collectSystemOps).
|
|
231
|
+
*/
|
|
232
|
+
export function loadConfig() {
|
|
233
|
+
const configPath = findConfigFile();
|
|
234
|
+
if (configPath) {
|
|
235
|
+
return JSON.parse(readFileSync(configPath, "utf-8"));
|
|
236
|
+
}
|
|
237
|
+
return {};
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Chunk } from "./ingest.js";
|
|
2
|
+
/**
|
|
3
|
+
* Initialize the embedding pipeline (downloads model on first run, ~22MB).
|
|
4
|
+
* Subsequent calls use cached model.
|
|
5
|
+
*
|
|
6
|
+
* @huggingface/transformers is an optional dependency (~250MB transitive,
|
|
7
|
+
* native onnxruntime binaries). If it's absent (npm install --no-optional,
|
|
8
|
+
* air-gapped network at install time, or locked-down corp proxy), we silently
|
|
9
|
+
* fall back to BM25 keyword search — `search_context` still works.
|
|
10
|
+
*/
|
|
11
|
+
export declare function initEmbeddings(): Promise<boolean>;
|
|
12
|
+
/**
|
|
13
|
+
* An embedded chunk with its vector.
|
|
14
|
+
*/
|
|
15
|
+
export interface EmbeddedChunk {
|
|
16
|
+
chunk: Chunk;
|
|
17
|
+
vector: Float32Array;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Embed all chunks. Returns the chunks with their vectors.
|
|
21
|
+
* Shows progress on stderr.
|
|
22
|
+
*/
|
|
23
|
+
export declare function embedChunks(chunks: Chunk[]): Promise<EmbeddedChunk[]>;
|
|
24
|
+
export interface VectorSearchResult {
|
|
25
|
+
chunk: Chunk;
|
|
26
|
+
score: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Semantic search: embed the query, then find most similar chunks.
|
|
30
|
+
*/
|
|
31
|
+
export declare function vectorSearch(query: string, embeddedChunks: EmbeddedChunk[], topK?: number): Promise<VectorSearchResult[]>;
|
|
32
|
+
/**
|
|
33
|
+
* Check if embeddings are available.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isEmbeddingsReady(): boolean;
|
|
36
|
+
//# sourceMappingURL=embeddings.d.ts.map
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// LOCKED — verified March 3 2026 — Xenova all-MiniLM-L6-v2 local CPU embeddings + disk cache
|
|
2
|
+
// DO NOT RE-AUDIT — stable since v1.0, no API keys, no data leaves machine
|
|
3
|
+
//
|
|
4
|
+
// 🔒 LOCKED [OPTIONAL-HF] — 2026-06-10
|
|
5
|
+
// ⛔ NEVER convert `await import("@huggingface/transformers")` to a static
|
|
6
|
+
// import. HF is in optionalDependencies — a static import breaks installs
|
|
7
|
+
// that ran with --omit=optional (locked-down npm proxies, air-gapped CI).
|
|
8
|
+
// ⛔ NEVER remove the try/catch around the dynamic import or the
|
|
9
|
+
// isMissingDep detection branch — both keep the MCP server alive on
|
|
10
|
+
// fresh installs where HF didn't download.
|
|
11
|
+
// WHY: The package was failing to install on enterprise environments because
|
|
12
|
+
// HF transitively pulls 427 MB of onnxruntime native binaries. Making
|
|
13
|
+
// HF optional dropped cold install from 547 MB to 120 MB and unblocked
|
|
14
|
+
// a whole class of buyers. A static import would silently re-break this.
|
|
15
|
+
// FIX: If you need to take a dependency on a transformer feature, add it
|
|
16
|
+
// behind the same dynamic-import + isEmbeddingsReady() check pattern.
|
|
17
|
+
// We dynamically import @huggingface/transformers to keep startup fast
|
|
18
|
+
// and handle the case where it fails gracefully.
|
|
19
|
+
let embedPipeline = null;
|
|
20
|
+
const MODEL_NAME = "Xenova/all-MiniLM-L6-v2";
|
|
21
|
+
/**
|
|
22
|
+
* Initialize the embedding pipeline (downloads model on first run, ~22MB).
|
|
23
|
+
* Subsequent calls use cached model.
|
|
24
|
+
*
|
|
25
|
+
* @huggingface/transformers is an optional dependency (~250MB transitive,
|
|
26
|
+
* native onnxruntime binaries). If it's absent (npm install --no-optional,
|
|
27
|
+
* air-gapped network at install time, or locked-down corp proxy), we silently
|
|
28
|
+
* fall back to BM25 keyword search — `search_context` still works.
|
|
29
|
+
*/
|
|
30
|
+
export async function initEmbeddings() {
|
|
31
|
+
try {
|
|
32
|
+
console.error(`[ContextEngine] 🧠 Loading embedding model: ${MODEL_NAME}...`);
|
|
33
|
+
const { pipeline } = await import("@huggingface/transformers");
|
|
34
|
+
embedPipeline = await pipeline("feature-extraction", MODEL_NAME, {
|
|
35
|
+
dtype: "fp32",
|
|
36
|
+
});
|
|
37
|
+
console.error(`[ContextEngine] ✅ Embedding model loaded`);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
const msg = err.message;
|
|
42
|
+
const isMissingDep = msg.includes("Cannot find package") ||
|
|
43
|
+
msg.includes("ERR_MODULE_NOT_FOUND") ||
|
|
44
|
+
msg.includes("@huggingface/transformers");
|
|
45
|
+
if (isMissingDep) {
|
|
46
|
+
console.error(`[ContextEngine] ⚠ Semantic search disabled — @huggingface/transformers not installed.\n` +
|
|
47
|
+
` BM25 keyword search still works. To enable semantic search:\n` +
|
|
48
|
+
` npm install @huggingface/transformers\n` +
|
|
49
|
+
` (~250MB download; CPU-only embeddings via Xenova/all-MiniLM-L6-v2, no data leaves the machine).`);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.error(`[ContextEngine] ⚠ Embeddings unavailable (keyword search only):`, msg);
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Embed a single text string → float32 vector (384 dimensions).
|
|
59
|
+
*/
|
|
60
|
+
async function embedText(text) {
|
|
61
|
+
const output = await embedPipeline(text, {
|
|
62
|
+
pooling: "mean",
|
|
63
|
+
normalize: true,
|
|
64
|
+
});
|
|
65
|
+
// output.data is a flat typed array
|
|
66
|
+
return new Float32Array(output.data);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Cosine similarity between two vectors.
|
|
70
|
+
* Both must be normalized (which MiniLM + normalize:true guarantees),
|
|
71
|
+
* so dot product = cosine similarity.
|
|
72
|
+
*/
|
|
73
|
+
function cosineSimilarity(a, b) {
|
|
74
|
+
let dot = 0;
|
|
75
|
+
for (let i = 0; i < a.length; i++) {
|
|
76
|
+
dot += a[i] * b[i];
|
|
77
|
+
}
|
|
78
|
+
return dot;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Embed all chunks. Returns the chunks with their vectors.
|
|
82
|
+
* Shows progress on stderr.
|
|
83
|
+
*/
|
|
84
|
+
export async function embedChunks(chunks) {
|
|
85
|
+
const results = [];
|
|
86
|
+
const total = chunks.length;
|
|
87
|
+
const batchSize = 10;
|
|
88
|
+
for (let i = 0; i < total; i += batchSize) {
|
|
89
|
+
const batch = chunks.slice(i, i + batchSize);
|
|
90
|
+
const batchResults = await Promise.all(batch.map(async (chunk) => {
|
|
91
|
+
// Embed section + content together for context
|
|
92
|
+
const text = `${chunk.section}\n${chunk.content}`.slice(0, 512);
|
|
93
|
+
const vector = await embedText(text);
|
|
94
|
+
return { chunk, vector };
|
|
95
|
+
}));
|
|
96
|
+
results.push(...batchResults);
|
|
97
|
+
const done = Math.min(i + batchSize, total);
|
|
98
|
+
if (done % 50 === 0 || done === total) {
|
|
99
|
+
console.error(`[ContextEngine] 📊 Embedded ${done}/${total} chunks`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return results;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Semantic search: embed the query, then find most similar chunks.
|
|
106
|
+
*/
|
|
107
|
+
export async function vectorSearch(query, embeddedChunks, topK = 10) {
|
|
108
|
+
if (!embedPipeline || embeddedChunks.length === 0)
|
|
109
|
+
return [];
|
|
110
|
+
const queryVector = await embedText(query);
|
|
111
|
+
const scored = embeddedChunks.map((ec) => ({
|
|
112
|
+
chunk: ec.chunk,
|
|
113
|
+
score: cosineSimilarity(queryVector, ec.vector),
|
|
114
|
+
}));
|
|
115
|
+
scored.sort((a, b) => b.score - a.score);
|
|
116
|
+
return scored.slice(0, topK);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Check if embeddings are available.
|
|
120
|
+
*/
|
|
121
|
+
export function isEmbeddingsReady() {
|
|
122
|
+
return embedPipeline !== null;
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=embeddings.js.map
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
export interface Obligation {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
status: "ok" | "warn" | "fail";
|
|
5
|
+
detail: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Callback type for searching learnings — avoids circular import.
|
|
9
|
+
* Returns top matches with rule text + optional project scope.
|
|
10
|
+
*/
|
|
11
|
+
export type LearningSearchFn = (query: string, projects?: string[]) => Array<{
|
|
12
|
+
rule: string;
|
|
13
|
+
project?: string;
|
|
14
|
+
category: string;
|
|
15
|
+
}>;
|
|
16
|
+
export declare class ProtocolFirewall {
|
|
17
|
+
private toolCalls;
|
|
18
|
+
private learningsSaved;
|
|
19
|
+
private sessionSaved;
|
|
20
|
+
private readonly startTime;
|
|
21
|
+
private nudgesIssued;
|
|
22
|
+
private searchRecalls;
|
|
23
|
+
private truncations;
|
|
24
|
+
private lastNonExemptCall;
|
|
25
|
+
private round;
|
|
26
|
+
private roundAtLastSave;
|
|
27
|
+
private roundsSinceSessionSave;
|
|
28
|
+
private lastSessionSaveTime;
|
|
29
|
+
private statsFlushTimer;
|
|
30
|
+
private static readonly STATS_FLUSH_MS;
|
|
31
|
+
private static readonly STATS_FILE;
|
|
32
|
+
private learningSearchFn;
|
|
33
|
+
private activeProjects;
|
|
34
|
+
private injectionCache;
|
|
35
|
+
private injectionCacheRound;
|
|
36
|
+
private learningsInjected;
|
|
37
|
+
private gitCache;
|
|
38
|
+
private docCache;
|
|
39
|
+
private projectDirs;
|
|
40
|
+
constructor(opts?: {
|
|
41
|
+
skipRestore?: boolean;
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* Resume enforcement from a prior session if it crashed recently.
|
|
45
|
+
* Reads session-stats.json and restores round counters so a crashed
|
|
46
|
+
* window doesn't reset enforcement back to silent.
|
|
47
|
+
*/
|
|
48
|
+
private loadPriorState;
|
|
49
|
+
/**
|
|
50
|
+
* Update project directories (call during reindex).
|
|
51
|
+
*/
|
|
52
|
+
setProjectDirs(dirs: Array<{
|
|
53
|
+
path: string;
|
|
54
|
+
name: string;
|
|
55
|
+
}>): void;
|
|
56
|
+
/**
|
|
57
|
+
* Register the learning search function.
|
|
58
|
+
* Call once at startup to enable auto-injection without circular imports.
|
|
59
|
+
*/
|
|
60
|
+
setLearningSearchFn(fn: LearningSearchFn): void;
|
|
61
|
+
/**
|
|
62
|
+
* Wrap a tool response with protocol status + learning injection.
|
|
63
|
+
* This is the ONLY public API. Call on every tool response.
|
|
64
|
+
*
|
|
65
|
+
* - Exempt tools (save_learning, etc.) pass through unmodified
|
|
66
|
+
* - Silent phase (first 10 calls or 0 obligations): no change
|
|
67
|
+
* - Footer/Header: status block appended/prepended
|
|
68
|
+
* - Degraded: response TRUNCATED + status block
|
|
69
|
+
*
|
|
70
|
+
* @param toolName MCP tool name
|
|
71
|
+
* @param responseText Original tool response text
|
|
72
|
+
* @param contextHint Optional query/args string for learning injection
|
|
73
|
+
*/
|
|
74
|
+
wrap(toolName: string, responseText: string, contextHint?: string): string;
|
|
75
|
+
/**
|
|
76
|
+
* Get current state for diagnostics / testing.
|
|
77
|
+
*/
|
|
78
|
+
getState(): {
|
|
79
|
+
toolCalls: number;
|
|
80
|
+
learningsSaved: number;
|
|
81
|
+
sessionSaved: boolean;
|
|
82
|
+
uptimeMinutes: number;
|
|
83
|
+
nudgesIssued: number;
|
|
84
|
+
searchRecalls: number;
|
|
85
|
+
truncations: number;
|
|
86
|
+
timeSavedMinutes: number;
|
|
87
|
+
round: number;
|
|
88
|
+
roundsSinceSessionSave: number;
|
|
89
|
+
learningsInjected: number;
|
|
90
|
+
sessionOverdue: boolean;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Record that N learnings were surfaced in a search result.
|
|
94
|
+
* Call from search_context handler after counting learning-sourced results.
|
|
95
|
+
*/
|
|
96
|
+
recordSearchRecalls(count: number): void;
|
|
97
|
+
/**
|
|
98
|
+
* Search and format relevant learnings for injection into tool response.
|
|
99
|
+
* Returns null if no relevant learnings or no search function registered.
|
|
100
|
+
* Results are cached per round to avoid repeated searches for same hint.
|
|
101
|
+
*/
|
|
102
|
+
private buildLearningInjection;
|
|
103
|
+
/**
|
|
104
|
+
* Estimate minutes saved by ContextEngine this session.
|
|
105
|
+
* Only counts genuine value events — not overhead like nudges or auto-injection.
|
|
106
|
+
* - Each explicit search recall ≈ 2 min (avoids re-discovery / googling)
|
|
107
|
+
* - Each auto-injected learning ≈ 1 min (proactive context, less than explicit)
|
|
108
|
+
* - Each learning saved ≈ 1 min (future sessions benefit)
|
|
109
|
+
* - Session save ≈ 3 min (avoids cold-start next session)
|
|
110
|
+
* Note: nudges removed (they're enforcement overhead, not time saved).
|
|
111
|
+
*/
|
|
112
|
+
private estimateTimeSaved;
|
|
113
|
+
private scheduleStatsFlush;
|
|
114
|
+
private flushStats;
|
|
115
|
+
private recordCompliance;
|
|
116
|
+
/**
|
|
117
|
+
* Check if session save is overdue (>10 minutes since last save,
|
|
118
|
+
* or >10 minutes of activity without ever saving).
|
|
119
|
+
* Only triggers after the warmup period (first 10 minutes of session).
|
|
120
|
+
*/
|
|
121
|
+
private isSessionOverdue;
|
|
122
|
+
/**
|
|
123
|
+
* Build an urgent session reminder block.
|
|
124
|
+
* This is injected at the TOP of every tool response when overdue.
|
|
125
|
+
*/
|
|
126
|
+
private buildSessionUrgentBlock;
|
|
127
|
+
private evaluate;
|
|
128
|
+
private checkGit;
|
|
129
|
+
private checkDocs;
|
|
130
|
+
private computeLevel;
|
|
131
|
+
private formatBlock;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=firewall.d.ts.map
|