@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.
Files changed (48) hide show
  1. package/CHANGELOG.md +313 -0
  2. package/LICENSE +83 -0
  3. package/README.md +470 -0
  4. package/defaults/learnings.json +146 -0
  5. package/dist/activation.d.ts +48 -0
  6. package/dist/activation.js +377 -0
  7. package/dist/adapters.d.ts +101 -0
  8. package/dist/adapters.js +171 -0
  9. package/dist/agents.d.ts +137 -0
  10. package/dist/agents.js +1638 -0
  11. package/dist/audit.d.ts +23 -0
  12. package/dist/audit.js +163 -0
  13. package/dist/cache.d.ts +15 -0
  14. package/dist/cache.js +117 -0
  15. package/dist/claude-integration.d.ts +95 -0
  16. package/dist/claude-integration.js +247 -0
  17. package/dist/cli.d.ts +18 -0
  18. package/dist/cli.js +1823 -0
  19. package/dist/code-chunker.d.ts +12 -0
  20. package/dist/code-chunker.js +270 -0
  21. package/dist/collectors.d.ts +63 -0
  22. package/dist/collectors.js +617 -0
  23. package/dist/config.d.ts +73 -0
  24. package/dist/config.js +239 -0
  25. package/dist/embeddings.d.ts +36 -0
  26. package/dist/embeddings.js +124 -0
  27. package/dist/firewall.d.ts +133 -0
  28. package/dist/firewall.js +631 -0
  29. package/dist/hooks.d.ts +76 -0
  30. package/dist/hooks.js +313 -0
  31. package/dist/index.d.ts +3 -0
  32. package/dist/index.js +1081 -0
  33. package/dist/ingest.d.ts +32 -0
  34. package/dist/ingest.js +162 -0
  35. package/dist/learnings.d.ts +108 -0
  36. package/dist/learnings.js +714 -0
  37. package/dist/license-sig.d.ts +47 -0
  38. package/dist/license-sig.js +104 -0
  39. package/dist/policy.d.ts +131 -0
  40. package/dist/policy.js +182 -0
  41. package/dist/search.d.ts +11 -0
  42. package/dist/search.js +99 -0
  43. package/dist/sessions.d.ts +46 -0
  44. package/dist/sessions.js +153 -0
  45. package/examples/adapters/notion-adapter.js +108 -0
  46. package/examples/adapters/rss-adapter.js +76 -0
  47. package/package.json +87 -0
  48. package/skills/opscontext/SKILL.md +260 -0
@@ -0,0 +1,153 @@
1
+ // LOCKED — verified March 3 2026 — session persistence: save/load/list/delete + auto-session inject
2
+ // DO NOT RE-AUDIT — 16 session tests passing, stable since v1.16.0
3
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, unlinkSync } from "fs";
4
+ import { join } from "path";
5
+ import { homedir } from "os";
6
+ import { safeAppend } from "./audit.js";
7
+ /**
8
+ * Session Persistence — save/restore conversation context between sessions.
9
+ *
10
+ * Sessions are saved as JSON files in ~/.contextengine/sessions/.
11
+ * Each session has a name, timestamp, and key-value data store.
12
+ *
13
+ * Use cases:
14
+ * - AI agent saves decisions/context at end of session → picks up next time
15
+ * - Track what was discussed, changed, or planned across sessions
16
+ * - Store project-specific notes that persist between agent restarts
17
+ */
18
+ const SESSIONS_DIR = join(homedir(), ".contextengine", "sessions");
19
+ function ensureDir() {
20
+ if (!existsSync(SESSIONS_DIR)) {
21
+ mkdirSync(SESSIONS_DIR, { recursive: true });
22
+ }
23
+ }
24
+ function sessionPath(name) {
25
+ // Sanitize name for filesystem
26
+ const safe = name.replace(/[^a-zA-Z0-9_\-\.]/g, "_").substring(0, 100);
27
+ return join(SESSIONS_DIR, `${safe}.json`);
28
+ }
29
+ /**
30
+ * Save or update a key-value pair in a named session.
31
+ */
32
+ export function saveSession(name, key, value) {
33
+ ensureDir();
34
+ const path = sessionPath(name);
35
+ const now = new Date().toISOString();
36
+ let session;
37
+ if (existsSync(path)) {
38
+ session = JSON.parse(readFileSync(path, "utf-8"));
39
+ session.updated = now;
40
+ }
41
+ else {
42
+ session = { name, created: now, updated: now, entries: [] };
43
+ }
44
+ // Update existing key or add new one
45
+ const existing = session.entries.find((e) => e.key === key);
46
+ if (existing) {
47
+ existing.value = value;
48
+ existing.timestamp = now;
49
+ }
50
+ else {
51
+ session.entries.push({ key, value, timestamp: now });
52
+ }
53
+ writeFileSync(path, JSON.stringify(session, null, 2));
54
+ safeAppend("session.save", {
55
+ name: session.name,
56
+ key,
57
+ value_length: value.length,
58
+ entries: session.entries.length,
59
+ });
60
+ return session;
61
+ }
62
+ /**
63
+ * Load a session by name.
64
+ */
65
+ export function loadSession(name) {
66
+ const path = sessionPath(name);
67
+ if (!existsSync(path))
68
+ return null;
69
+ try {
70
+ return JSON.parse(readFileSync(path, "utf-8"));
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ }
76
+ /**
77
+ * List all saved sessions.
78
+ */
79
+ export function listSessions() {
80
+ ensureDir();
81
+ try {
82
+ return readdirSync(SESSIONS_DIR)
83
+ .filter((f) => f.endsWith(".json"))
84
+ .map((f) => {
85
+ try {
86
+ const session = JSON.parse(readFileSync(join(SESSIONS_DIR, f), "utf-8"));
87
+ return {
88
+ name: session.name,
89
+ entries: session.entries.length,
90
+ created: session.created,
91
+ updated: session.updated,
92
+ };
93
+ }
94
+ catch {
95
+ return null;
96
+ }
97
+ })
98
+ .filter(Boolean);
99
+ }
100
+ catch {
101
+ return [];
102
+ }
103
+ }
104
+ /**
105
+ * Delete a session.
106
+ */
107
+ export function deleteSession(name) {
108
+ const path = sessionPath(name);
109
+ if (existsSync(path)) {
110
+ unlinkSync(path);
111
+ safeAppend("session.delete", { name });
112
+ return true;
113
+ }
114
+ return false;
115
+ }
116
+ /**
117
+ * Format a session for display.
118
+ */
119
+ export function formatSession(session) {
120
+ const lines = [];
121
+ lines.push(`# Session: ${session.name}`);
122
+ lines.push(`Created: ${session.created}`);
123
+ lines.push(`Updated: ${session.updated}`);
124
+ lines.push(`Entries: ${session.entries.length}`);
125
+ lines.push("");
126
+ for (const entry of session.entries) {
127
+ lines.push(`## ${entry.key}`);
128
+ lines.push(`_Updated: ${entry.timestamp}_`);
129
+ lines.push("");
130
+ lines.push(entry.value);
131
+ lines.push("");
132
+ }
133
+ return lines.join("\n");
134
+ }
135
+ /**
136
+ * Format session list for display.
137
+ */
138
+ export function formatSessionList(sessions) {
139
+ if (sessions.length === 0) {
140
+ return "No saved sessions. Use `save_session` to create one.";
141
+ }
142
+ const lines = [];
143
+ lines.push("# 📋 Saved Sessions\n");
144
+ lines.push("| Session | Entries | Created | Last Updated |");
145
+ lines.push("|---------|---------|---------|-------------|");
146
+ for (const s of sessions) {
147
+ const created = s.created.split("T")[0];
148
+ const updated = s.updated.split("T")[0];
149
+ lines.push(`| ${s.name} | ${s.entries} | ${created} | ${updated} |`);
150
+ }
151
+ return lines.join("\n");
152
+ }
153
+ //# sourceMappingURL=sessions.js.map
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Example ContextEngine Adapter — Notion Integration
3
+ *
4
+ * This is a SKELETON adapter showing how to build a custom data source
5
+ * connector for ContextEngine. It demonstrates the full Adapter interface.
6
+ *
7
+ * To use this adapter:
8
+ * 1. Copy this file to your project
9
+ * 2. Install the Notion SDK: npm install @notionhq/client
10
+ * 3. Add to contextengine.json:
11
+ * {
12
+ * "adapters": [{
13
+ * "name": "notion",
14
+ * "module": "./adapters/notion-adapter.js",
15
+ * "config": { "token": "$NOTION_API_TOKEN", "databases": ["db-id-1", "db-id-2"] }
16
+ * }]
17
+ * }
18
+ * 4. Set NOTION_API_TOKEN in your environment
19
+ *
20
+ * @module
21
+ */
22
+
23
+ // Import the Adapter type from ContextEngine
24
+ // import type { Adapter } from "@compr/contextengine-mcp/adapters";
25
+
26
+ /**
27
+ * Notion adapter — fetches pages and databases from Notion
28
+ * and converts them into searchable ContextEngine chunks.
29
+ */
30
+ const notionAdapter = {
31
+ name: "notion",
32
+ description: "Fetches pages and databases from Notion workspace",
33
+
34
+ /**
35
+ * Validate adapter configuration.
36
+ * Return null if valid, error message string if invalid.
37
+ */
38
+ validate(config) {
39
+ if (!config?.token) {
40
+ return "Missing 'token' in config. Set NOTION_API_TOKEN env var and use \"$NOTION_API_TOKEN\" in config.";
41
+ }
42
+ return null;
43
+ },
44
+
45
+ /**
46
+ * Optional initialization — called once when adapter loads.
47
+ * Use for auth handshake, API client setup, etc.
48
+ */
49
+ async init(config) {
50
+ // Example: Initialize Notion client
51
+ // const { Client } = await import("@notionhq/client");
52
+ // this._client = new Client({ auth: config.token });
53
+ console.error(`[Notion Adapter] Initialized with token: ${config?.token ? "✓" : "✗"}`);
54
+ },
55
+
56
+ /**
57
+ * Collect data — the core method.
58
+ * Called during every reindex. Must be safe (read-only, no side effects).
59
+ * Returns Chunk[] compatible with ContextEngine's search index.
60
+ */
61
+ async collect(config) {
62
+ const chunks = [];
63
+
64
+ try {
65
+ // Example: Fetch pages from configured databases
66
+ const databases = config?.databases || [];
67
+
68
+ for (const dbId of databases) {
69
+ // In a real adapter, you'd call the Notion API here:
70
+ // const response = await this._client.databases.query({ database_id: dbId });
71
+ // for (const page of response.results) { ... }
72
+
73
+ // Example chunk structure:
74
+ chunks.push({
75
+ source: `Notion DB ${dbId}`,
76
+ section: "## Page Title",
77
+ content: "Page content extracted from Notion blocks...",
78
+ lineStart: 1,
79
+ lineEnd: 1,
80
+ // Optional: add indexedAt for temporal decay
81
+ indexedAt: new Date().toISOString(),
82
+ });
83
+ }
84
+ } catch (err) {
85
+ // Never throw — return empty array on failure
86
+ console.error(`[Notion Adapter] Error: ${err?.message || err}`);
87
+ return [];
88
+ }
89
+
90
+ return chunks;
91
+ },
92
+
93
+ /**
94
+ * Optional cleanup — called on server shutdown.
95
+ */
96
+ async destroy() {
97
+ // Close connections, flush buffers, etc.
98
+ console.error("[Notion Adapter] Destroyed");
99
+ },
100
+ };
101
+
102
+ // Export as default (ContextEngine auto-detects)
103
+ export default notionAdapter;
104
+
105
+ // Alternative: export a factory function for per-instance config
106
+ // export function createAdapter(config) {
107
+ // return { ...notionAdapter, _config: config };
108
+ // }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Example ContextEngine Adapter — RSS/Atom Feed
3
+ *
4
+ * A minimal adapter that fetches RSS/Atom feeds and indexes them
5
+ * as searchable chunks. Good starting point for simple adapters.
6
+ *
7
+ * Usage in contextengine.json:
8
+ * {
9
+ * "adapters": [{
10
+ * "name": "feeds",
11
+ * "module": "./adapters/rss-adapter.js",
12
+ * "config": {
13
+ * "feeds": [
14
+ * "https://blog.example.com/rss.xml",
15
+ * "https://changelog.example.com/feed.atom"
16
+ * ],
17
+ * "maxItems": 20
18
+ * }
19
+ * }]
20
+ * }
21
+ *
22
+ * @module
23
+ */
24
+
25
+ export function createAdapter(config) {
26
+ const maxItems = config?.maxItems || 10;
27
+
28
+ return {
29
+ name: "rss-feed",
30
+ description: `RSS/Atom feed indexer (max ${maxItems} items per feed)`,
31
+
32
+ validate(cfg) {
33
+ if (!cfg?.feeds || !Array.isArray(cfg.feeds) || cfg.feeds.length === 0) {
34
+ return "Missing 'feeds' array in config. Provide at least one RSS/Atom URL.";
35
+ }
36
+ return null;
37
+ },
38
+
39
+ async collect(cfg) {
40
+ const feeds = cfg?.feeds || [];
41
+ const chunks = [];
42
+
43
+ for (const feedUrl of feeds) {
44
+ try {
45
+ const response = await fetch(feedUrl);
46
+ if (!response.ok) continue;
47
+ const text = await response.text();
48
+
49
+ // Simple XML parsing — extract <item> or <entry> elements
50
+ const items = text.match(/<(?:item|entry)[\s>][\s\S]*?<\/(?:item|entry)>/gi) || [];
51
+
52
+ for (const item of items.slice(0, maxItems)) {
53
+ const title = item.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1]?.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, "$1")?.trim() || "Untitled";
54
+ const description = item.match(/<(?:description|summary|content)[^>]*>([\s\S]*?)<\/(?:description|summary|content)>/i)?.[1]?.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, "$1")?.replace(/<[^>]+>/g, "")?.trim() || "";
55
+ const pubDate = item.match(/<(?:pubDate|published|updated)[^>]*>([\s\S]*?)<\/(?:pubDate|published|updated)>/i)?.[1]?.trim();
56
+
57
+ if (title || description) {
58
+ chunks.push({
59
+ source: new URL(feedUrl).hostname,
60
+ section: `## ${title}`,
61
+ content: description.slice(0, 2000),
62
+ lineStart: 1,
63
+ lineEnd: 1,
64
+ indexedAt: pubDate ? new Date(pubDate).toISOString() : new Date().toISOString(),
65
+ });
66
+ }
67
+ }
68
+ } catch {
69
+ // Skip failed feeds silently
70
+ }
71
+ }
72
+
73
+ return chunks;
74
+ },
75
+ };
76
+ }
package/package.json ADDED
@@ -0,0 +1,87 @@
1
+ {
2
+ "name": "@compr/opscontext-mcp",
3
+ "version": "2.0.0",
4
+ "description": "OpsContext for AI Agents — read-only fleet visibility (PM2/nginx/Docker/git/cron) + tamper-evident audit log + policy-as-code hooks. The ops + compliance layer Claude Code can't grow natively.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "opscontext": "dist/cli.js",
9
+ "opscontext-mcp": "dist/cli.js",
10
+ "contextengine": "dist/cli.js",
11
+ "contextengine-mcp": "dist/cli.js"
12
+ },
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "dev": "tsc --watch",
16
+ "start": "node dist/index.js",
17
+ "test": "vitest run",
18
+ "test:watch": "vitest",
19
+ "lint": "eslint src/",
20
+ "prepublishOnly": "node scripts/check-npm-token-expiry.mjs && npm run build",
21
+ "check-token": "node scripts/check-npm-token-expiry.mjs"
22
+ },
23
+ "keywords": [
24
+ "mcp",
25
+ "model-context-protocol",
26
+ "claude-code",
27
+ "claude",
28
+ "cursor",
29
+ "copilot",
30
+ "ai-agents",
31
+ "ops",
32
+ "observability",
33
+ "audit-log",
34
+ "compliance",
35
+ "soc2",
36
+ "iso27001",
37
+ "policy-as-code",
38
+ "git-hooks",
39
+ "pre-commit",
40
+ "secret-scanning",
41
+ "fleet",
42
+ "context",
43
+ "knowledge-base",
44
+ "developer-tools"
45
+ ],
46
+ "author": {
47
+ "name": "FASTPROD (PROD LLC)",
48
+ "url": "https://compr.fr"
49
+ },
50
+ "license": "BSL-1.1",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/FASTPROD/ContextEngine.git"
54
+ },
55
+ "homepage": "https://compr.fr",
56
+ "bugs": {
57
+ "email": "yannick@compr.ch"
58
+ },
59
+ "engines": {
60
+ "node": ">=18.0.0"
61
+ },
62
+ "files": [
63
+ "dist/",
64
+ "!dist/test*",
65
+ "!dist/**/*.map",
66
+ "defaults/",
67
+ "skills/",
68
+ "examples/",
69
+ "LICENSE",
70
+ "README.md",
71
+ "CHANGELOG.md"
72
+ ],
73
+ "dependencies": {
74
+ "@modelcontextprotocol/sdk": "^1.26.0",
75
+ "zod": "^4.3.6"
76
+ },
77
+ "optionalDependencies": {
78
+ "@huggingface/transformers": "^3.8.1"
79
+ },
80
+ "devDependencies": {
81
+ "@types/node": "^25.2.3",
82
+ "eslint": "^10.0.1",
83
+ "typescript": "^5.9.3",
84
+ "typescript-eslint": "^8.56.0",
85
+ "vitest": "^4.0.18"
86
+ }
87
+ }
@@ -0,0 +1,260 @@
1
+ ---
2
+ name: opscontext
3
+ description: "OpsContext for AI Agents — the ops + compliance layer Claude Code can't grow natively. Read-only fleet visibility (PM2/nginx/Docker/git/cron) + tamper-evident hash-chained audit log (SOC2 CC7.2 / ISO 27001 A.12.4.1) + declarative policy-as-code git hooks (.contextengine/policy.json with secret_patterns, doc_coverage, deploy_verify_hosts, bypass_tokens) + persistent learnings + hybrid BM25/semantic search. Use when: (1) searching project documentation or context files, (2) collecting operational insights from workspaces, (3) storing and retrieving persistent learnings across sessions, (4) auditing project compliance or AI-readiness, (5) managing session data, (6) verifying the audit log chain, (7) authoring/validating policy.json. Zero API keys — runs 100% locally with CPU embeddings."
4
+ homepage: https://www.npmjs.com/package/@compr/opscontext-mcp
5
+ metadata: { "openclaw": { "emoji": "🧭", "requires": { "bins": ["npx"] }, "homepage": "https://www.npmjs.com/package/@compr/opscontext-mcp" } }
6
+ ---
7
+
8
+ # OpsContext for AI Agents — Knowledge Base + Ops + Compliance
9
+
10
+ ContextEngine turns your project documentation into a **queryable knowledge base** with hybrid BM25 keyword + semantic vector search. Zero API keys required — embeddings run locally on CPU.
11
+
12
+ ## Quick Start
13
+
14
+ ### 1. Initialize (one-time per project)
15
+
16
+ ```bash
17
+ npx @compr/contextengine-mcp init
18
+ ```
19
+
20
+ Creates `contextengine.json` config + `.github/copilot-instructions.md` template in the current directory.
21
+
22
+ ### 2. Search your knowledge base
23
+
24
+ ```bash
25
+ # Ask the agent to search for context
26
+ search_context "deployment docker nginx setup"
27
+ ```
28
+
29
+ ContextEngine auto-discovers documentation files from 7 common patterns:
30
+ - `.github/copilot-instructions.md`
31
+ - `.github/SKILLS.md`
32
+ - `CLAUDE.md`
33
+ - `.cursorrules`
34
+ - `.cursor/rules`
35
+ - `AGENTS.md`
36
+
37
+ ## CLI Usage (no MCP required)
38
+
39
+ ContextEngine also works as a standalone CLI tool — no MCP client needed:
40
+
41
+ ```bash
42
+ npx @compr/contextengine-mcp search "docker nginx" # Search knowledge base
43
+ npx @compr/contextengine-mcp list-sources # Show indexed sources
44
+ npx @compr/contextengine-mcp list-projects # Discover all projects
45
+ npx @compr/contextengine-mcp score # AI-readiness score
46
+ npx @compr/contextengine-mcp score --html # Visual HTML report
47
+ npx @compr/contextengine-mcp list-learnings security # List learnings by category
48
+ npx @compr/contextengine-mcp audit # Compliance audit
49
+ npx @compr/contextengine-mcp help # Show all commands
50
+ ```
51
+
52
+ ## MCP Server Setup
53
+
54
+ ContextEngine runs as an **MCP server** via stdio transport. Configure it in your MCP client:
55
+
56
+ ### VS Code (Per-Workspace)
57
+
58
+ Add to `.vscode/mcp.json` in your project root:
59
+
60
+ ```json
61
+ {
62
+ "servers": {
63
+ "contextengine": {
64
+ "command": "npx",
65
+ "args": ["-y", "@compr/contextengine-mcp"],
66
+ "env": {
67
+ "CONTEXTENGINE_WORKSPACES": "/path/to/your/projects"
68
+ }
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ ### Claude Desktop
75
+
76
+ Add to Claude Desktop MCP config:
77
+
78
+ ```json
79
+ {
80
+ "mcpServers": {
81
+ "contextengine": {
82
+ "command": "npx",
83
+ "args": ["-y", "@compr/contextengine-mcp"],
84
+ "env": {
85
+ "CONTEXTENGINE_WORKSPACES": "/path/to/your/projects"
86
+ }
87
+ }
88
+ }
89
+ }
90
+ ```
91
+
92
+ ### OpenClaw MCP Config
93
+
94
+ Add to your OpenClaw `openclaw.json` MCP servers section:
95
+
96
+ ```json
97
+ {
98
+ "mcpServers": {
99
+ "contextengine": {
100
+ "command": "npx",
101
+ "args": ["-y", "@compr/contextengine-mcp"],
102
+ "env": {
103
+ "CONTEXTENGINE_WORKSPACES": "/path/to/your/projects"
104
+ }
105
+ }
106
+ }
107
+ }
108
+ ```
109
+
110
+ ## Available Tools (17)
111
+
112
+ | Tool | Description |
113
+ |------|-------------|
114
+ | `search_context` | Hybrid BM25+semantic search with temporal decay. Modes: hybrid, keyword, semantic |
115
+ | `list_sources` | Show all indexed sources with chunk counts and embedding status |
116
+ | `read_source` | Read full content of a knowledge source by name |
117
+ | `reindex` | Force full re-index of all sources |
118
+ | `list_projects` | Discover and analyze all projects (tech stack, git, docker, pm2) |
119
+ | `check_ports` | Scan all projects for port conflicts |
120
+ | `run_audit` | Compliance agent — git remotes, hooks, .env, Docker, PM2, versions |
121
+ | `score_project` | AI-readiness scoring 0-100% with anti-gaming v2 (symlink/ghost config detection) |
122
+ | `save_session` | Save key-value entry to a named session for cross-session persistence |
123
+ | `load_session` | Load all entries from a named session |
124
+ | `list_sessions` | List all saved sessions with entry counts and timestamps |
125
+ | `end_session` | Pre-flight checklist — checks uncommitted git changes + doc freshness |
126
+ | `save_learning` | Save a permanent operational rule — auto-surfaces in search results |
127
+ | `list_learnings` | List all permanent learnings, optionally filtered by category |
128
+ | `import_learnings` | Bulk-import learnings from Markdown or JSON files |
129
+ | `activate` | Activate a Pro license on this machine |
130
+ | `activation_status` | Check current license activation status |
131
+
132
+ ## Core Capabilities
133
+
134
+ ### Hybrid Search
135
+
136
+ Combines three signals for optimal relevance:
137
+ - **40% BM25 keyword search** — IDF-weighted, rare terms rank higher
138
+ - **60% semantic similarity** — cosine distance via MiniLM-L6-v2 (22MB, local CPU)
139
+ - **Temporal decay** — 90-day half-life boosts recent content
140
+
141
+ ```bash
142
+ # Search with mode selection
143
+ search_context "docker nginx proxy" --mode hybrid
144
+ search_context "deployment steps" --mode keyword
145
+ search_context "how to configure SSL" --mode semantic
146
+ ```
147
+
148
+ ### Operational Data Collection
149
+
150
+ Auto-collects from your projects (no setup needed):
151
+ - **Git**: branches, remotes, recent commits, hooks
152
+ - **package.json / composer.json**: dependencies, scripts
153
+ - **Docker**: Dockerfile, docker-compose services
154
+ - **PM2**: process list, ecosystem config
155
+ - **Nginx**: server blocks, proxy configs
156
+ - **.env**: variable names (never values)
157
+ - **Cron**: scheduled tasks
158
+
159
+ ### Persistent Learnings
160
+
161
+ Save reusable patterns, bug fixes, and operational rules that auto-surface in search results:
162
+
163
+ ```bash
164
+ # Save a learning
165
+ save_learning --category "deployment" --rule "Always use --platform linux/amd64 for cross-arch Docker builds" --context "Apple Silicon to AMD64 server"
166
+
167
+ # List by category
168
+ list_learnings --category "security"
169
+ ```
170
+
171
+ 16 categories: architecture, security, bug-patterns, deployment, testing, api, frontend, backend, infrastructure, tooling, devops, git, data, dependencies, performance, accessibility.
172
+
173
+ ### Session Persistence
174
+
175
+ Save and restore key-value data across sessions via MCP tools (NOT files in the project):
176
+
177
+ ```bash
178
+ # Sessions are stored centrally in ~/.contextengine/sessions/ — NOT in the project directory
179
+ # Use the MCP tool or CLI command — do NOT create session files manually
180
+
181
+ save_session --name "project-x" --key "current_task" --value "Implementing auth flow"
182
+ load_session --name "project-x"
183
+ ```
184
+
185
+ **Important**: "Save session" means calling the `save_session` MCP tool or CLI command. It does NOT mean creating a markdown file. Sessions are stored in `~/.contextengine/sessions/` and auto-loaded on MCP startup.
186
+
187
+ ## Configuration
188
+
189
+ Create `contextengine.json` in your project root (or run `npx @compr/contextengine-mcp init`):
190
+
191
+ ```json
192
+ {
193
+ "sources": ["docs/architecture.md", "RUNBOOK.md"],
194
+ "workspaces": ["/home/user/projects"],
195
+ "patterns": [".github/copilot-instructions.md", "CLAUDE.md"],
196
+ "codeDirs": ["src", "lib"]
197
+ }
198
+ ```
199
+
200
+ ### Environment Variables
201
+
202
+ | Variable | Purpose |
203
+ |----------|---------|
204
+ | `CONTEXTENGINE_CONFIG` | Path to contextengine.json config file |
205
+ | `CONTEXTENGINE_WORKSPACES` | Colon-separated list of workspace directories |
206
+
207
+ ## Architecture
208
+
209
+ - **Embeddings**: all-MiniLM-L6-v2 via @huggingface/transformers (22MB, local CPU, no API key)
210
+ - **Transport**: MCP SDK v1.26 via stdio
211
+ - **Chunking**: Markdown heading-based with 4-line overlap + code chunker (TS/JS/Python)
212
+ - **Dedup**: SHA-256 content hashing prevents duplicate chunks
213
+ - **Caching**: Embedding cache to disk (~/.contextengine/embedding-cache.json)
214
+ - **File watching**: fs.watch with 500ms debounce → auto re-index on changes
215
+ - **Non-blocking**: Keyword search available instantly, embeddings load in background
216
+
217
+ ## Notes
218
+
219
+ - Requires **Node.js 18+**
220
+ - First run downloads the embedding model (~22MB) — subsequent runs use cache
221
+ - Keyword search is available instantly at startup; semantic search becomes available once the model loads
222
+ - License: BSL-1.1 (Business Source License)
223
+ - npm: `@compr/contextengine-mcp`
224
+
225
+ ### Post-Commit Verification
226
+
227
+ After every `git push`, call `end_session` to verify nothing was missed:
228
+
229
+ - **MCP tool**: `end_session` — use when ContextEngine MCP server is connected
230
+ - **CLI fallback**: `npx @compr/contextengine-mcp end-session` — use when MCP is not connected (Cursor, Copilot, terminal sessions)
231
+
232
+ Resolve any FAIL items before finishing. Workflow: `commit → push → end_session → verify → fix anything it catches → re-commit if needed`.
233
+
234
+ ## Lock Markers — Do Not Touch Verified Code
235
+
236
+ ContextEngine detects **lock markers** in source files and documentation. When a chunk contains a lock marker, search results display a `🔒 LOCKED` prefix — this means the code has been verified and **must not be modified, deleted, or re-implemented**.
237
+
238
+ ### Recognized patterns
239
+
240
+ ```
241
+ // LOCKED // in JS/TS/C/Go
242
+ /* LOCKED */ // block comment variant
243
+ # LOCKED // Python/Shell/YAML
244
+ <!-- LOCKED --> // HTML/Markdown
245
+ LOCKED — verified <date> // prose
246
+ DO NOT RE-AUDIT // audit skip signal
247
+ ALREADY IMPLEMENTED // status marker
248
+ VERIFIED — DO NOT // verified block
249
+ ```
250
+
251
+ ### Rules for agents
252
+
253
+ 1. **Never delete or rewrite** code that contains a lock marker or sits within a locked section
254
+ 2. **Never re-implement** functionality described in an `ALREADY IMPLEMENTED` block
255
+ 3. **When you finish implementing a feature**, add a lock marker to signal future sessions:
256
+ ```ts
257
+ // LOCKED — verified March 3 2026 — GA4 tag injection
258
+ ```
259
+ 4. **When you see `🔒 LOCKED` in search results**, treat it as read-only context — do not open the file to "fix" or "improve" it
260
+ 5. **If you must modify locked code** (user explicitly requests it), remove the lock marker first and add a new one after verification