From bf4e268b95fb69c10833b815ad73fdbded7f6911 Mon Sep 17 00:00:00 2001 From: rajkumarsakthivel Date: Fri, 7 Aug 2026 11:54:32 +0100 Subject: [PATCH 01/10] docs: add Agent Plugins integration design spec Design for adopting the Agent Plugins v1.0.0 standard as a new distribution channel. Covers plugin generation (plugin.json, mcp.json, SKILL.md), project auto-discovery in cce serve, instruction template consolidation, and CLI interface. --- .../specs/2026-08-07-agent-plugins-design.md | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-07-agent-plugins-design.md diff --git a/docs/superpowers/specs/2026-08-07-agent-plugins-design.md b/docs/superpowers/specs/2026-08-07-agent-plugins-design.md new file mode 100644 index 0000000..2f1f815 --- /dev/null +++ b/docs/superpowers/specs/2026-08-07-agent-plugins-design.md @@ -0,0 +1,211 @@ +# Agent Plugins Integration for CCE + +**Date:** 2026-08-07 +**Status:** Draft +**Scope:** Add Agent Plugins v1.0.0 support to CCE as a new distribution channel + +## Problem + +CCE's onboarding requires multiple steps: install the Python package, run `cce init`, restart the editor. Each step loses users. The `cce init` command maintains 8 editor-specific config writers and 6 instruction file formats. Instruction blocks written to repos go stale when CCE adds new tools. + +Agent Plugins is an open standard (v1.0.0) backed by Amazon, Cursor, Microsoft, OpenAI, and Vercel. Compatible clients include VS Code, GitHub Copilot, ChatGPT, Codex, Cursor, and Kiro. Adopting it gives CCE a zero-friction install path for 6 major clients and eliminates instruction staleness for those clients. + +## Goals + +1. Users can install CCE as an Agent Plugin with zero prior setup +2. Instructions stay in sync with CCE version (no stale repo files) +3. Existing `cce init` flow is unaffected (additive only) +4. Implementation is small (3 generated files, 1 new CLI flag, 1 `cce serve` enhancement) + +## Non-Goals + +- Drop any existing `cce init --agent` support +- Add client extensions (`com.cce/` namespace) +- Bundle the Python package inside the plugin +- Publish to a plugin registry (future PR once registries stabilize) +- Support multiple skills (CCE is one product, one skill) + +## Design + +### 1. Plugin Output Structure + +`cce init --plugin` generates a self-contained Agent Plugin directory: + +``` +/ +├── plugin.json +├── mcp.json +├── skills/ +│ └── code-context/ +│ ├── SKILL.md +│ └── references/ +│ └── tools.md +└── LICENSE +``` + +Default output path: `.cce/plugin/` inside the project directory. +Override with `--plugin-dir ` for custom locations or global installs. + +### 2. plugin.json + +Minimal manifest per the Agent Plugins v1.0.0 spec: + +```json +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "code-context-engine", + "version": "", + "description": "Index your codebase. AI searches instead of re-reading files. 94% token savings.", + "author": { + "name": "Elara Labs", + "url": "https://github.com/elara-labs/code-context-engine" + }, + "repository": "https://github.com/elara-labs/code-context-engine", + "license": "MIT", + "keywords": ["code-search", "token-savings", "mcp", "code-indexing", "retrieval"] +} +``` + +The `version` field is read from `importlib.metadata.version("code-context-engine")` at generation time, keeping it in sync with the installed CCE version. + +### 3. mcp.json + +```json +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", + "mcpServers": { + "code-context-engine": { + "type": "stdio", + "command": "uvx", + "args": ["--from", "code-context-engine[local]", "cce", "serve"] + } + } +} +``` + +Design decisions: + +- **`uvx` as command:** Users don't need CCE pre-installed. `uvx` fetches and runs it on demand. `uvx` is a bare executable name, valid per the spec. Falls back gracefully if `uvx` is not installed (client reports server failed to start). +- **No `--project-dir`:** The MCP server discovers the project from cwd (see section 5). This avoids hardcoding paths and keeps the plugin portable. +- **No `cwd` override:** The spec defaults cwd to plugin root. Workspace-aware clients (Cursor, some VS Code modes) override cwd to the workspace. Either way, `cce serve` handles it via auto-discovery. +- **`[local]` extra:** Includes fastembed + ONNX Runtime so the plugin works without Ollama. Users with Ollama can install without `[local]` separately. + +### 4. SKILL.md + +Frontmatter per the Agent Skills specification: + +```yaml +--- +name: code-context +description: > + Intelligent code retrieval and cross-session memory for AI coding agents. + Use when searching codebases, answering questions about code, exploring + architecture, finding functions or patterns, or recalling past decisions. + Provides context_search, expand_chunk, related_context, session_recall, + record_decision, record_code_area, and set_output_compression MCP tools. +license: MIT +compatibility: Requires Python 3.11+ and uv (or uvx) +metadata: + author: elara-labs + version: "" +--- +``` + +The body contains the agent-neutral instruction text. This is the same content currently in `_CCE_INSTRUCTIONS_BASE` in `editors.py`, covering: + +- When and how to use `context_search` instead of reading files directly +- Available tools: `expand_chunk`, `related_context`, `session_recall` +- Cross-session memory: `record_decision`, `record_code_area` +- Output compression: `set_output_compression` + +The `references/tools.md` file contains per-tool parameter documentation (parameters, types, examples, edge cases). The agent loads this on demand per the progressive disclosure model, keeping SKILL.md under 500 lines. + +### 5. Project Auto-Discovery in `cce serve` + +When `--project-dir` is not provided, `cce serve` walks up from cwd to find the project root: + +``` +1. Start at cwd +2. At current directory, check for .context-engine.yaml or .git/ + (if both exist, .context-engine.yaml wins as it's an explicit CCE marker) +3. If either is found, use that directory as the project root +4. If neither is found, move to parent directory and repeat +5. Stop at filesystem root +6. If no project found, start server but return a helpful message + on first tool call: "No project found. Run `cce init` in your + project directory, or start with `cce serve --project-dir `." +``` + +This matches the convention used by git, npm, cargo, and similar tools. + +This change benefits all users, not just plugin users. Anyone running `cce serve` from a subdirectory gets the right project automatically. + +When the project is found via auto-discovery, `cce serve` reloads config from that directory's `.context-engine.yaml` (same as the existing `--project-dir` behavior). + +### 6. Instruction Template Consolidation + +Extract the instruction text into a standalone file at `src/context_engine/data/instructions.md`. This file becomes the single source of truth for: + +- **Plugin path:** SKILL.md body content (copied verbatim) +- **Existing agent path:** `write_instruction_file()` reads it for .cursorrules, AGENTS.md, GEMINI.md, TABNINE.md, .github/copilot-instructions.md +- **Claude Code path:** `_build_claude_md_block()` reads it and appends Claude-specific extras (session_timeline, session_event, stricter language) + +The file is plain markdown with no frontmatter. Output compression rules are appended dynamically based on config, same as today. + +### 7. CLI Interface + +``` +cce init --plugin # generate plugin at .cce/plugin/ +cce init --plugin --plugin-dir ~/p/ # custom output directory +cce init --agent claude --plugin # both: agent config + plugin +``` + +`--plugin` is independent of `--agent`. They can be used together or separately. + +When `--plugin` is used: +1. Generate plugin.json with version from installed CCE +2. Generate mcp.json with uvx command +3. Generate skills/code-context/SKILL.md from instruction template +4. Generate skills/code-context/references/tools.md with per-tool docs +5. Copy LICENSE from CCE package +6. Add `.cce/plugin/` to .gitignore (if default path) + +### 8. Version Synchronization + +The plugin.json `version` and SKILL.md metadata `version` are set from `importlib.metadata.version("code-context-engine")` at generation time. This follows the same pattern as `server.json` version syncing (per existing project convention). + +Users regenerate the plugin after `cce upgrade` to pick up new versions. A future enhancement could auto-regenerate the plugin during `cce upgrade` if the plugin directory exists. + +## File Changes + +| File | Change | +|------|--------| +| `src/context_engine/cli.py` | Add `--plugin` and `--plugin-dir` to `cce init`. Add project auto-discovery to `serve()`. | +| `src/context_engine/editors.py` | Add `generate_plugin()` function. Refactor `_CCE_INSTRUCTIONS_BASE` to read from `data/instructions.md`. | +| `src/context_engine/data/instructions.md` | New file. Extracted instruction template (single source of truth). | +| `tests/test_plugin.py` | New file. Tests for plugin generation and project auto-discovery. | + +## Testing + +1. **Plugin generation:** `cce init --plugin` produces valid plugin.json, mcp.json, and SKILL.md with correct schema URLs, version, and instruction content. +2. **Plugin validation:** Generated plugin.json and mcp.json pass JSON Schema validation against the Agent Plugins v1.0.0 schemas. +3. **SKILL.md validation:** Frontmatter has required `name` and `description` fields. Name matches directory name (`code-context`). Body is non-empty. +4. **Project auto-discovery:** `cce serve` without `--project-dir` finds the project from a subdirectory. Returns helpful error when no project found. Respects `.context-engine.yaml` over `.git/` when both exist at different levels. +5. **Instruction consolidation:** All instruction writers (plugin, agent files, CLAUDE.md) produce the same base content from the shared template. +6. **Backward compatibility:** Existing `cce init --agent claude` behavior is unchanged. Existing `cce serve --project-dir` behavior is unchanged. + +## Risks and Mitigations + +| Risk | Mitigation | +|------|------------| +| `uvx` not installed on user's machine | Client reports "server failed to start." SKILL.md can include a fallback note. Plugin is still useful for its skill content even without MCP. | +| Client sets cwd to plugin root (not workspace) | Auto-discovery walks up from cwd. If plugin is inside the project (`.cce/plugin/`), it finds the project two levels up. If plugin is global, auto-discovery won't find a project, and the server returns a helpful error. | +| Agent Plugins spec changes in v2 | Plugin generation is isolated in one function. Schema URLs are easy to update. | +| Claude Code doesn't support Agent Plugins yet | No impact. Claude Code continues using `.mcp.json` + `CLAUDE.md` via existing `cce init --agent claude`. | + +## Future Work (out of scope for this PR) + +- Publish plugin to VS Code Marketplace or other plugin registries +- Auto-regenerate plugin during `cce upgrade` +- `cce init --plugin --no-uvx` variant for environments where `cce` is already on PATH +- Streamable HTTP transport option for remote/containerized CCE From 33ded78d61be326635f0524391e5ebb59ee554ba Mon Sep 17 00:00:00 2001 From: rajkumarsakthivel Date: Fri, 7 Aug 2026 23:13:18 +0100 Subject: [PATCH 02/10] docs: add Agent Plugins implementation plan --- .../plans/2026-08-07-agent-plugins.md | 833 ++++++++++++++++++ 1 file changed, 833 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-07-agent-plugins.md diff --git a/docs/superpowers/plans/2026-08-07-agent-plugins.md b/docs/superpowers/plans/2026-08-07-agent-plugins.md new file mode 100644 index 0000000..ef1d8d1 --- /dev/null +++ b/docs/superpowers/plans/2026-08-07-agent-plugins.md @@ -0,0 +1,833 @@ +# Agent Plugins Integration Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add Agent Plugins v1.0.0 support to CCE so users can install CCE as a portable plugin in VS Code, Cursor, Copilot, Codex, ChatGPT, and Kiro with zero prior setup. + +**Architecture:** A new `generate_plugin()` function in `editors.py` writes the three-file plugin directory (plugin.json, mcp.json, skills/code-context/SKILL.md). The instruction template is extracted from `_CCE_INSTRUCTIONS_BASE` into a standalone file shared by all writers. `cce serve` gains project auto-discovery (walk up from cwd to find .git or .context-engine.yaml) so the plugin's MCP server works without `--project-dir`. + +**Tech Stack:** Python 3.11+, Click CLI, Agent Plugins v1.0.0, Agent Skills v1.0.0 + +## Global Constraints + +- Agent Plugins v1.0.0 schema URL: `https://agent-plugins.org/schemas/1.0.0/plugin.schema.json` +- Agent Plugins mcp.json schema URL: `https://agent-plugins.org/schemas/1.0.0/mcp.schema.json` +- Plugin name must be 1-64 chars, lowercase alphanumeric/hyphens/periods only +- SKILL.md `name` field must match its parent directory name +- SKILL.md body should be under 500 lines +- Never use dashes as punctuation in documentation or README files +- Do not add Co-Authored-By lines to commits +- Update server.json whenever pyproject.toml version bumps (existing convention applies to plugin.json too) +- Run `uv run python -m pytest tests/ -x -q` to verify all tests pass + +--- + +### Task 1: Extract instruction template into standalone file + +Extract `_CCE_INSTRUCTIONS_BASE` from `editors.py` into `src/context_engine/data/instructions.md` and make all instruction writers read from it. + +**Files:** +- Create: `src/context_engine/data/__init__.py` +- Create: `src/context_engine/data/instructions.md` +- Modify: `src/context_engine/editors.py:101-138` +- Test: `tests/test_plugin.py` (new) + +**Interfaces:** +- Consumes: nothing +- Produces: `get_instructions_base() -> str` function in `editors.py` that reads `data/instructions.md`. All existing callers of `_CCE_INSTRUCTIONS_BASE` use this instead. + +- [ ] **Step 1: Write the failing test** + +Create `tests/test_plugin.py`: + +```python +"""Tests for Agent Plugins generation and instruction template.""" +from pathlib import Path + +from context_engine.editors import get_instructions_base + + +def test_instructions_base_loads_from_file(): + """Instruction template must load from data/instructions.md.""" + text = get_instructions_base() + assert "context_search" in text + assert "record_decision" in text + assert "session_recall" in text + assert len(text) > 100 + + +def test_instructions_base_has_no_claude_specific_content(): + """The base template is agent-neutral (no Claude Code references).""" + text = get_instructions_base() + assert "Claude Code" not in text + assert "CLAUDE.md" not in text +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `uv run python -m pytest tests/test_plugin.py::test_instructions_base_loads_from_file -v` +Expected: FAIL with `ImportError` (function doesn't exist yet) + +- [ ] **Step 3: Create the data directory and instructions file** + +Create `src/context_engine/data/__init__.py` (empty file). + +Create `src/context_engine/data/instructions.md` with the content currently in `_CCE_INSTRUCTIONS_BASE` (lines 101-129 of `editors.py`): + +```markdown +## Context Engine (CCE) + +This project uses Code Context Engine for intelligent code retrieval and +cross-session memory. + +### Searching the codebase + +**Use `context_search` instead of reading files directly** when exploring +the codebase, answering questions about code, or understanding how things +work. `context_search` returns the most relevant code chunks with +confidence scores instead of whole files. + +When to use `context_search`: +- Answering questions about the codebase ("how does X work?", "where is Y?") +- Exploring structure or architecture +- Finding related code, functions, or patterns + +Other tools: +- `expand_chunk` for full source of a compressed result +- `related_context` for what calls/imports a function +- `session_recall` to recall past decisions + +### Cross-session memory + +Call `session_recall("topic phrase")` before answering non-trivial questions. +Call `record_decision(decision="...", reason="...")` after making choices. +Call `record_code_area(file_path="...", description="...")` after meaningful work. +``` + +- [ ] **Step 4: Add `get_instructions_base()` and refactor editors.py** + +In `editors.py`, replace the `_CCE_INSTRUCTIONS_BASE` string literal and `_build_instructions` with: + +```python +from functools import lru_cache + +@lru_cache(maxsize=1) +def get_instructions_base() -> str: + """Load the agent-neutral instruction template from data/instructions.md.""" + path = Path(__file__).parent / "data" / "instructions.md" + return path.read_text(encoding="utf-8") + + +def _build_instructions(output_level: str = "standard") -> str: + """Build CCE instructions with the configured output style.""" + from context_engine.compression.output_rules import get_instruction_output_block + base = get_instructions_base() + block = get_instruction_output_block(output_level) + if block: + return base + "\n" + block + "\n" + return base +``` + +Remove the old `_CCE_INSTRUCTIONS_BASE` string constant. Update `_CCE_INSTRUCTIONS` to call the refactored `_build_instructions`. + +- [ ] **Step 5: Run tests to verify they pass** + +Run: `uv run python -m pytest tests/test_plugin.py -v` +Expected: PASS (both tests) + +Run: `uv run python -m pytest tests/ -x -q` +Expected: All existing tests still pass (no regressions from refactor) + +- [ ] **Step 6: Commit** + +```bash +git add src/context_engine/data/__init__.py src/context_engine/data/instructions.md src/context_engine/editors.py tests/test_plugin.py +git commit -m "refactor: extract instruction template into data/instructions.md" +``` + +--- + +### Task 2: Add `generate_plugin()` function + +Implement plugin directory generation in `editors.py`. + +**Files:** +- Modify: `src/context_engine/editors.py` +- Create: `src/context_engine/data/tools_reference.md` +- Modify: `tests/test_plugin.py` + +**Interfaces:** +- Consumes: `get_instructions_base()` from Task 1 +- Produces: `generate_plugin(output_dir: Path, version: str, output_level: str = "standard") -> None` that writes plugin.json, mcp.json, skills/code-context/SKILL.md, and skills/code-context/references/tools.md to `output_dir`. + +- [ ] **Step 1: Write the failing tests** + +Append to `tests/test_plugin.py`: + +```python +import json + +import yaml + +from context_engine.editors import generate_plugin + + +def test_generate_plugin_creates_structure(tmp_path): + """generate_plugin writes the complete Agent Plugin directory.""" + out = tmp_path / "plugin" + generate_plugin(out, version="1.2.3") + + assert (out / "plugin.json").exists() + assert (out / "mcp.json").exists() + assert (out / "skills" / "code-context" / "SKILL.md").exists() + assert (out / "skills" / "code-context" / "references" / "tools.md").exists() + + +def test_plugin_json_schema(tmp_path): + """plugin.json must conform to Agent Plugins v1.0.0.""" + out = tmp_path / "plugin" + generate_plugin(out, version="1.2.3") + + data = json.loads((out / "plugin.json").read_text()) + assert data["$schema"] == "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json" + assert data["name"] == "code-context-engine" + assert data["version"] == "1.2.3" + assert data["license"] == "MIT" + + +def test_mcp_json_schema(tmp_path): + """mcp.json must declare stdio transport with uvx.""" + out = tmp_path / "plugin" + generate_plugin(out, version="1.2.3") + + data = json.loads((out / "mcp.json").read_text()) + assert data["$schema"] == "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json" + server = data["mcpServers"]["code-context-engine"] + assert server["type"] == "stdio" + assert server["command"] == "uvx" + assert "cce" in server["args"] + assert "serve" in server["args"] + + +def test_skill_md_frontmatter(tmp_path): + """SKILL.md must have valid Agent Skills frontmatter.""" + out = tmp_path / "plugin" + generate_plugin(out, version="1.2.3") + + content = (out / "skills" / "code-context" / "SKILL.md").read_text() + # Split frontmatter + assert content.startswith("---\n") + parts = content.split("---\n", 2) + fm = yaml.safe_load(parts[1]) + assert fm["name"] == "code-context" + assert len(fm["description"]) > 10 + assert fm["license"] == "MIT" + assert fm["metadata"]["version"] == "1.2.3" + + +def test_skill_md_body_has_instructions(tmp_path): + """SKILL.md body must contain the instruction template content.""" + out = tmp_path / "plugin" + generate_plugin(out, version="1.2.3") + + content = (out / "skills" / "code-context" / "SKILL.md").read_text() + assert "context_search" in content + assert "record_decision" in content + + +def test_generate_plugin_overwrites_existing(tmp_path): + """Regenerating into the same directory overwrites cleanly.""" + out = tmp_path / "plugin" + generate_plugin(out, version="1.0.0") + generate_plugin(out, version="2.0.0") + + data = json.loads((out / "plugin.json").read_text()) + assert data["version"] == "2.0.0" +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `uv run python -m pytest tests/test_plugin.py::test_generate_plugin_creates_structure -v` +Expected: FAIL with `ImportError` (function doesn't exist yet) + +- [ ] **Step 3: Create tools_reference.md** + +Create `src/context_engine/data/tools_reference.md`: + +```markdown +# MCP Tools Reference + +Detailed parameter documentation for Code Context Engine's MCP tools. +Loaded on demand by the agent when more detail is needed. + +## context_search + +Search the codebase using hybrid vector + BM25 retrieval. + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| query | string | yes | | Natural language query | +| top_k | integer | no | 10 | Maximum results to return | +| max_tokens | integer | no | 8000 | Token budget for results | + +Returns ranked code chunks with confidence scores. Use this instead of +Read, Grep, or Glob when exploring code. + +## expand_chunk + +Get the full original content for a compressed chunk. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| chunk_id | string | yes | ID from a context_search result | + +## related_context + +Find related code via graph edges (calls, imports). + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| chunk_id | string | yes | ID from a context_search result | + +## session_recall + +Recall past decisions and turn summaries via topic search. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| topic | string | yes | Topic phrase (not a single word) | + +Pass a descriptive phrase, not a single word. e.g. `session_recall("auth flow")` +not `session_recall("auth")`. + +## session_timeline + +List turn summaries for a session, oldest first. Use to drill into a +session_id returned by session_recall. + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| session_id | string | yes | | Session ID from recall results | +| limit | integer | no | 20 | Max turns to return | + +## session_event + +Return raw input/output payload for a single tool event. Use to drill +into an event_id from session_timeline. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| event_id | integer | yes | Event ID from timeline results | + +## record_decision + +Record a decision with reasoning for future session_recall. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| decision | string | yes | What was decided | +| reason | string | yes | Why this choice was made | + +## record_code_area + +Record a code area worked on for future session_recall. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| file_path | string | yes | Path to the file | +| description | string | yes | What was done | + +## index_status + +Check when the index was last updated. No parameters. + +## reindex + +Trigger re-indexing of a file or the full project. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| path | string | no | File path to re-index (omit for full project) | + +## set_output_compression + +Set output compression level to reduce response token cost. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| level | string | yes | `off`, `lite`, `standard`, or `max` | + +Levels: off = normal output, lite = no filler (~30% savings), +standard = fragments (~65% savings), max = telegraphic (~75% savings). +Code blocks and commands are never compressed. +``` + +- [ ] **Step 4: Implement `generate_plugin()`** + +Add to `editors.py`: + +```python +def generate_plugin( + output_dir: Path, + version: str, + output_level: str = "standard", +) -> None: + """Generate an Agent Plugins v1.0.0 directory. + + Writes plugin.json, mcp.json, and skills/code-context/SKILL.md to + ``output_dir``. Safe to call repeatedly (overwrites existing files). + """ + output_dir.mkdir(parents=True, exist_ok=True) + + # plugin.json + plugin_manifest = { + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "code-context-engine", + "version": version, + "description": ( + "Index your codebase. AI searches instead of re-reading files. " + "94% token savings." + ), + "author": { + "name": "Elara Labs", + "url": "https://github.com/elara-labs/code-context-engine", + }, + "repository": "https://github.com/elara-labs/code-context-engine", + "license": "MIT", + "keywords": [ + "code-search", + "token-savings", + "mcp", + "code-indexing", + "retrieval", + ], + } + (output_dir / "plugin.json").write_text( + json.dumps(plugin_manifest, indent=2) + "\n", encoding="utf-8" + ) + + # mcp.json + mcp_config = { + "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", + "mcpServers": { + "code-context-engine": { + "type": "stdio", + "command": "uvx", + "args": [ + "--from", + "code-context-engine[local]", + "cce", + "serve", + ], + }, + }, + } + (output_dir / "mcp.json").write_text( + json.dumps(mcp_config, indent=2) + "\n", encoding="utf-8" + ) + + # skills/code-context/SKILL.md + skill_dir = output_dir / "skills" / "code-context" + skill_dir.mkdir(parents=True, exist_ok=True) + + description = ( + "Intelligent code retrieval and cross-session memory for AI coding " + "agents. Use when searching codebases, answering questions about " + "code, exploring architecture, finding functions or patterns, or " + "recalling past decisions. Provides context_search, expand_chunk, " + "related_context, session_recall, record_decision, record_code_area, " + "and set_output_compression MCP tools." + ) + frontmatter = ( + "---\n" + "name: code-context\n" + f"description: >\n" + + "".join(f" {line}\n" for line in description.splitlines()) + + "license: MIT\n" + "compatibility: Requires Python 3.11+ and uv (or uvx)\n" + "metadata:\n" + " author: elara-labs\n" + f' version: "{version}"\n' + "---\n\n" + ) + body = _build_instructions(output_level) + (skill_dir / "SKILL.md").write_text(frontmatter + body, encoding="utf-8") + + # skills/code-context/references/tools.md + ref_dir = skill_dir / "references" + ref_dir.mkdir(parents=True, exist_ok=True) + tools_ref = (Path(__file__).parent / "data" / "tools_reference.md").read_text( + encoding="utf-8" + ) + (ref_dir / "tools.md").write_text(tools_ref, encoding="utf-8") + + # LICENSE (copy from package root if available) + pkg_license = Path(__file__).parent.parent.parent / "LICENSE" + if pkg_license.exists(): + (output_dir / "LICENSE").write_text( + pkg_license.read_text(encoding="utf-8"), encoding="utf-8" + ) +``` + +- [ ] **Step 5: Run tests to verify they pass** + +Run: `uv run python -m pytest tests/test_plugin.py -v` +Expected: All PASS + +Run: `uv run python -m pytest tests/ -x -q` +Expected: All existing tests still pass + +- [ ] **Step 6: Commit** + +```bash +git add src/context_engine/editors.py src/context_engine/data/tools_reference.md tests/test_plugin.py +git commit -m "feat: add generate_plugin() for Agent Plugins v1.0.0" +``` + +--- + +### Task 3: Add project auto-discovery to `cce serve` + +When `--project-dir` is not provided, walk up from cwd to find the project root. + +**Files:** +- Modify: `src/context_engine/cli.py:2793-2821` +- Modify: `tests/test_cli_serve.py` + +**Interfaces:** +- Consumes: nothing (standalone enhancement) +- Produces: `_discover_project_root(start: Path) -> Path | None` function in `cli.py` + +- [ ] **Step 1: Write the failing tests** + +Append to `tests/test_cli_serve.py`: + +```python +from context_engine.cli import _discover_project_root + + +def test_discover_project_root_finds_git(tmp_path): + """Walk-up finds .git directory.""" + project = tmp_path / "myproject" + project.mkdir() + (project / ".git").mkdir() + subdir = project / "src" / "deep" + subdir.mkdir(parents=True) + + assert _discover_project_root(subdir) == project + + +def test_discover_project_root_finds_context_engine_yaml(tmp_path): + """Walk-up finds .context-engine.yaml.""" + project = tmp_path / "myproject" + project.mkdir() + (project / ".context-engine.yaml").write_text("indexer:\n watch: true\n") + subdir = project / "src" + subdir.mkdir() + + assert _discover_project_root(subdir) == project + + +def test_discover_project_root_prefers_context_engine_yaml(tmp_path): + """When both .context-engine.yaml and .git exist at different levels, + .context-engine.yaml wins (found first on walk-up).""" + root = tmp_path / "root" + root.mkdir() + (root / ".git").mkdir() + inner = root / "inner" + inner.mkdir() + (inner / ".context-engine.yaml").write_text("") + + assert _discover_project_root(inner) == inner + + +def test_discover_project_root_returns_none(tmp_path): + """Returns None when no project markers found.""" + bare = tmp_path / "bare" + bare.mkdir() + + assert _discover_project_root(bare) is None +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `uv run python -m pytest tests/test_cli_serve.py::test_discover_project_root_finds_git -v` +Expected: FAIL with `ImportError` + +- [ ] **Step 3: Implement `_discover_project_root()`** + +Add to `cli.py` (near the top, after imports, around line 50): + +```python +def _discover_project_root(start: Path) -> Path | None: + """Walk up from *start* looking for a project root marker. + + Checks each directory for .context-engine.yaml (explicit CCE project) + or .git/ (any git repo). Returns the first match, or None at the + filesystem root. + """ + current = start.resolve() + while True: + if (current / ".context-engine.yaml").exists(): + return current + if (current / ".git").exists(): + return current + parent = current.parent + if parent == current: + return None + current = parent +``` + +- [ ] **Step 4: Wire auto-discovery into `serve()`** + +Modify the `serve()` function in `cli.py` (around line 2800). After the existing `if project_dir:` block, add an `else` branch: + +```python +def serve(ctx: click.Context, as_http: bool, host: str, port: int, project_dir: str | None) -> None: + """Start the MCP server (used by Claude Code).""" + if project_dir: + import os + os.chdir(project_dir) + target_config = Path(project_dir) / PROJECT_CONFIG_NAME + ctx.obj["config"] = load_config( + project_path=target_config if target_config.exists() else None + ) + else: + discovered = _discover_project_root(Path.cwd()) + if discovered and discovered != Path.cwd(): + import os + os.chdir(str(discovered)) + target_config = discovered / PROJECT_CONFIG_NAME + ctx.obj["config"] = load_config( + project_path=target_config if target_config.exists() else None + ) + # ... rest of function unchanged +``` + +- [ ] **Step 5: Run tests to verify they pass** + +Run: `uv run python -m pytest tests/test_cli_serve.py -v` +Expected: All PASS (new + existing) + +Run: `uv run python -m pytest tests/ -x -q` +Expected: All pass + +- [ ] **Step 6: Commit** + +```bash +git add src/context_engine/cli.py tests/test_cli_serve.py +git commit -m "feat(serve): auto-discover project root from cwd walk-up" +``` + +--- + +### Task 4: Add `--plugin` flag to `cce init` and update docs + +Wire plugin generation into the CLI, update README, wiki docs, and Starlight source. + +**Files:** +- Modify: `src/context_engine/cli.py:873-998` (init command) +- Modify: `README.md` +- Modify: `docs/wiki/CLI-Reference.md` +- Modify: `docs/wiki/Configuration.md` +- Modify: `docs-src/src/content/docs/getting-started.md` +- Modify: `docs-src/src/content/docs/cli-reference.md` +- Modify: `tests/test_plugin.py` + +**Interfaces:** +- Consumes: `generate_plugin()` from Task 2 +- Produces: `--plugin` and `--plugin-dir` options on `cce init` + +- [ ] **Step 1: Write the failing test** + +Append to `tests/test_plugin.py`: + +```python +from click.testing import CliRunner +from context_engine.cli import main + + +def test_init_plugin_flag(tmp_path): + """cce init --plugin generates the plugin directory.""" + runner = CliRunner() + project = tmp_path / "myproj" + project.mkdir() + (project / ".git").mkdir() + + result = runner.invoke(main, ["init", "--plugin", "--agent", "claude"], catch_exceptions=False, env={"HOME": str(tmp_path)}) + + plugin_dir = project / ".cce" / "plugin" + # The plugin files should exist even if init had other errors + # (embedding model download etc). Check the plugin output specifically. + assert (plugin_dir / "plugin.json").exists() or result.exit_code == 0 + + +def test_init_plugin_dir_flag(tmp_path): + """cce init --plugin --plugin-dir writes to custom path.""" + runner = CliRunner() + project = tmp_path / "myproj" + project.mkdir() + (project / ".git").mkdir() + custom = tmp_path / "custom-plugin" + + result = runner.invoke( + main, + ["init", "--plugin", "--plugin-dir", str(custom), "--agent", "claude"], + catch_exceptions=False, + env={"HOME": str(tmp_path)}, + ) + + assert (custom / "plugin.json").exists() or result.exit_code == 0 +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `uv run python -m pytest tests/test_plugin.py::test_init_plugin_flag -v` +Expected: FAIL (no `--plugin` option yet) + +- [ ] **Step 3: Add `--plugin` and `--plugin-dir` to `cce init`** + +Modify the `init` command definition in `cli.py`. Add options: + +```python +@main.command() +@click.option( + "--agent", + type=click.Choice(_INIT_AGENT_CHOICES), + default="auto", + show_default=True, + help="Agent/editor target: auto, claude, codex, copilot, pi, or all.", +) +@click.option("--plugin", "gen_plugin", is_flag=True, help="Generate an Agent Plugin directory") +@click.option("--plugin-dir", default=None, type=click.Path(), help="Plugin output directory (default: .cce/plugin/)") +@click.pass_context +def init(ctx: click.Context, agent: str, gen_plugin: bool, plugin_dir: str | None) -> None: +``` + +At the end of the `init` function (after indexing, before the "Done!" message), add: + +```python + # Agent Plugin generation + if gen_plugin: + from context_engine.editors import generate_plugin + from importlib.metadata import version as pkg_version + try: + ver = pkg_version("code-context-engine") + except Exception: + ver = "0.0.0" + plugin_out = Path(plugin_dir) if plugin_dir else project_dir / ".cce" / "plugin" + generate_plugin(plugin_out, version=ver, output_level=output_level) + click.echo(f" {_check()} Agent Plugin generated at {plugin_out}") + # Add default plugin path to .gitignore + if not plugin_dir: + gitignore = project_dir / ".gitignore" + gi_content = gitignore.read_text(encoding="utf-8") if gitignore.exists() else "" + if ".cce/plugin/" not in gi_content: + gitignore.write_text( + gi_content.rstrip() + "\n\n# CCE Agent Plugin (generated)\n.cce/plugin/\n", + encoding="utf-8", + ) +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `uv run python -m pytest tests/test_plugin.py -v` +Expected: All PASS + +Run: `uv run python -m pytest tests/ -x -q` +Expected: All pass + +- [ ] **Step 5: Update README.md** + +Add to the "Quick start" section after the `cce init` block: + +```markdown +> **Agent Plugin support:** Run `cce init --plugin` to generate a portable +> [Agent Plugin](https://agent-plugins.org) directory that works with +> VS Code, Cursor, Copilot, Codex, ChatGPT, and Kiro. +``` + +Add `cce init --plugin` to the "CLI at a glance" section: + +```markdown +cce init --plugin # Generate Agent Plugin for VS Code, Cursor, etc. +``` + +- [ ] **Step 6: Update docs/wiki/CLI-Reference.md** + +Add `--plugin` to the `cce init` section: + +```markdown +### Agent Plugin generation + +```bash +cce init --plugin # generate at .cce/plugin/ +cce init --plugin --plugin-dir ~/p/ # custom output directory +cce init --agent claude --plugin # both: agent config + plugin +``` + +Generates a portable [Agent Plugin](https://agent-plugins.org) directory +containing plugin.json, mcp.json, and a SKILL.md with CCE instructions. +Compatible with VS Code, Cursor, GitHub Copilot, Codex, ChatGPT, and Kiro. +The plugin uses `uvx` to launch CCE on demand, so users don't need to +pre-install the Python package. +``` + +Also add `--plugin` to the `cce list` command output section. + +- [ ] **Step 7: Update docs-src/src/content/docs/getting-started.md** + +Add a section about Agent Plugin installation after the manual install steps. + +- [ ] **Step 8: Update docs-src/src/content/docs/cli-reference.md** + +Add `--plugin` flag documentation to match wiki updates. + +- [ ] **Step 9: Run full test suite** + +Run: `uv run python -m pytest tests/ -x -q` +Expected: All pass + +- [ ] **Step 10: Commit** + +```bash +git add src/context_engine/cli.py tests/test_plugin.py README.md docs/wiki/CLI-Reference.md docs/wiki/Configuration.md docs-src/src/content/docs/getting-started.md docs-src/src/content/docs/cli-reference.md +git commit -m "feat: add --plugin flag to cce init for Agent Plugins v1.0.0 + +Generate a portable Agent Plugin directory (plugin.json, mcp.json, +SKILL.md) compatible with VS Code, Cursor, Copilot, Codex, ChatGPT, +and Kiro. Uses uvx to launch CCE on demand." +``` + +--- + +### Task 5: Create PR + +**Files:** none (git operations only) + +- [ ] **Step 1: Create branch and push** + +```bash +git checkout -b feat/agent-plugins +git push -u origin feat/agent-plugins +``` + +- [ ] **Step 2: Create PR** + +```bash +gh pr create --title "feat: Agent Plugins v1.0.0 support" --body "$(cat <<'EOF' +## Summary + +- Add `cce init --plugin` to generate a portable Agent Plugin directory +- Plugin works with VS Code, Cursor, GitHub Copilot, Codex, ChatGPT, and Kiro +- Uses `uvx` to launch CCE on demand (no pre-install needed) +- Extract instruction template into standalone file (single source of truth) +- Add project auto-discovery to `cce serve` (walk up from cwd to find .git) +- Update README, wiki docs, and Starlight source + +Closes # + +Design spec: docs/superpowers/specs/2026-08-07-agent-plugins-design.md +EOF +)" +``` From 036f584422fb401d6c73818cb84a443fea8d9b42 Mon Sep 17 00:00:00 2001 From: rajkumarsakthivel Date: Fri, 7 Aug 2026 23:17:01 +0100 Subject: [PATCH 03/10] refactor: extract instruction template into data/instructions.md --- src/context_engine/data/__init__.py | 0 src/context_engine/data/instructions.md | 27 ++++++++++++++++ src/context_engine/editors.py | 41 ++++++------------------- tests/test_plugin.py | 20 ++++++++++++ 4 files changed, 56 insertions(+), 32 deletions(-) create mode 100644 src/context_engine/data/__init__.py create mode 100644 src/context_engine/data/instructions.md create mode 100644 tests/test_plugin.py diff --git a/src/context_engine/data/__init__.py b/src/context_engine/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/context_engine/data/instructions.md b/src/context_engine/data/instructions.md new file mode 100644 index 0000000..90d226b --- /dev/null +++ b/src/context_engine/data/instructions.md @@ -0,0 +1,27 @@ +## Context Engine (CCE) + +This project uses Code Context Engine for intelligent code retrieval and +cross-session memory. + +### Searching the codebase + +**Use `context_search` instead of reading files directly** when exploring +the codebase, answering questions about code, or understanding how things +work. `context_search` returns the most relevant code chunks with +confidence scores instead of whole files. + +When to use `context_search`: +- Answering questions about the codebase ("how does X work?", "where is Y?") +- Exploring structure or architecture +- Finding related code, functions, or patterns + +Other tools: +- `expand_chunk` for full source of a compressed result +- `related_context` for what calls/imports a function +- `session_recall` to recall past decisions + +### Cross-session memory + +Call `session_recall("topic phrase")` before answering non-trivial questions. +Call `record_decision(decision="...", reason="...")` after making choices. +Call `record_code_area(file_path="...", description="...")` after meaningful work. diff --git a/src/context_engine/editors.py b/src/context_engine/editors.py index a75be55..a540d45 100644 --- a/src/context_engine/editors.py +++ b/src/context_engine/editors.py @@ -20,6 +20,7 @@ import json import re import tomllib +from functools import lru_cache from pathlib import Path from context_engine.utils import atomic_write_text, resolve_cce_binary @@ -97,45 +98,21 @@ # ── Instruction file definitions ────────────────────────────────────── -# Editor-agnostic CCE instructions (no "Claude Code" references) -_CCE_INSTRUCTIONS_BASE = """\ -## Context Engine (CCE) - -This project uses Code Context Engine for intelligent code retrieval and -cross-session memory. - -### Searching the codebase - -**Use `context_search` instead of reading files directly** when exploring -the codebase, answering questions about code, or understanding how things -work. `context_search` returns the most relevant code chunks with -confidence scores instead of whole files. - -When to use `context_search`: -- Answering questions about the codebase ("how does X work?", "where is Y?") -- Exploring structure or architecture -- Finding related code, functions, or patterns - -Other tools: -- `expand_chunk` for full source of a compressed result -- `related_context` for what calls/imports a function -- `session_recall` to recall past decisions - -### Cross-session memory - -Call `session_recall("topic phrase")` before answering non-trivial questions. -Call `record_decision(decision="...", reason="...")` after making choices. -Call `record_code_area(file_path="...", description="...")` after meaningful work. -""" +@lru_cache(maxsize=1) +def get_instructions_base() -> str: + """Load the agent-neutral instruction template from data/instructions.md.""" + path = Path(__file__).parent / "data" / "instructions.md" + return path.read_text(encoding="utf-8") def _build_instructions(output_level: str = "standard") -> str: """Build CCE instructions with the configured output style.""" from context_engine.compression.output_rules import get_instruction_output_block + base = get_instructions_base() block = get_instruction_output_block(output_level) if block: - return _CCE_INSTRUCTIONS_BASE + "\n" + block + "\n" - return _CCE_INSTRUCTIONS_BASE + return base + "\n" + block + "\n" + return base # Default instructions (standard output compression) diff --git a/tests/test_plugin.py b/tests/test_plugin.py new file mode 100644 index 0000000..8ecd67f --- /dev/null +++ b/tests/test_plugin.py @@ -0,0 +1,20 @@ +"""Tests for Agent Plugins generation and instruction template.""" +from pathlib import Path + +from context_engine.editors import get_instructions_base + + +def test_instructions_base_loads_from_file(): + """Instruction template must load from data/instructions.md.""" + text = get_instructions_base() + assert "context_search" in text + assert "record_decision" in text + assert "session_recall" in text + assert len(text) > 100 + + +def test_instructions_base_has_no_claude_specific_content(): + """The base template is agent-neutral (no Claude Code references).""" + text = get_instructions_base() + assert "Claude Code" not in text + assert "CLAUDE.md" not in text From 8728924c240d90ba5bb55b0b5dc03788c987b054 Mon Sep 17 00:00:00 2001 From: rajkumarsakthivel Date: Fri, 7 Aug 2026 23:18:36 +0100 Subject: [PATCH 04/10] feat: add generate_plugin() for Agent Plugins v1.0.0 --- src/context_engine/data/tools_reference.md | 105 +++++++++++++++++++++ src/context_engine/editors.py | 105 +++++++++++++++++++++ tests/test_plugin.py | 78 ++++++++++++++- 3 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 src/context_engine/data/tools_reference.md diff --git a/src/context_engine/data/tools_reference.md b/src/context_engine/data/tools_reference.md new file mode 100644 index 0000000..1ee6c1a --- /dev/null +++ b/src/context_engine/data/tools_reference.md @@ -0,0 +1,105 @@ +# MCP Tools Reference + +Detailed parameter documentation for Code Context Engine's MCP tools. +Loaded on demand by the agent when more detail is needed. + +## context_search + +Search the codebase using hybrid vector + BM25 retrieval. + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| query | string | yes | | Natural language query | +| top_k | integer | no | 10 | Maximum results to return | +| max_tokens | integer | no | 8000 | Token budget for results | + +Returns ranked code chunks with confidence scores. Use this instead of +Read, Grep, or Glob when exploring code. + +## expand_chunk + +Get the full original content for a compressed chunk. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| chunk_id | string | yes | ID from a context_search result | + +## related_context + +Find related code via graph edges (calls, imports). + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| chunk_id | string | yes | ID from a context_search result | + +## session_recall + +Recall past decisions and turn summaries via topic search. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| topic | string | yes | Topic phrase (not a single word) | + +Pass a descriptive phrase, not a single word. e.g. `session_recall("auth flow")` +not `session_recall("auth")`. + +## session_timeline + +List turn summaries for a session, oldest first. Use to drill into a +session_id returned by session_recall. + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| session_id | string | yes | | Session ID from recall results | +| limit | integer | no | 20 | Max turns to return | + +## session_event + +Return raw input/output payload for a single tool event. Use to drill +into an event_id from session_timeline. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| event_id | integer | yes | Event ID from timeline results | + +## record_decision + +Record a decision with reasoning for future session_recall. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| decision | string | yes | What was decided | +| reason | string | yes | Why this choice was made | + +## record_code_area + +Record a code area worked on for future session_recall. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| file_path | string | yes | Path to the file | +| description | string | yes | What was done | + +## index_status + +Check when the index was last updated. No parameters. + +## reindex + +Trigger re-indexing of a file or the full project. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| path | string | no | File path to re-index (omit for full project) | + +## set_output_compression + +Set output compression level to reduce response token cost. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| level | string | yes | `off`, `lite`, `standard`, or `max` | + +Levels: off = normal output, lite = no filler (~30% savings), +standard = fragments (~65% savings), max = telegraphic (~75% savings). +Code blocks and commands are never compressed. diff --git a/src/context_engine/editors.py b/src/context_engine/editors.py index a540d45..3abfec1 100644 --- a/src/context_engine/editors.py +++ b/src/context_engine/editors.py @@ -682,3 +682,108 @@ def remove_instruction_file(project_dir: Path, file_key: str) -> str | None: else: path.unlink() return f"Removed {info['name']}" + + +# ── Agent Plugin generation ────────────────────────────────────────── + + +def generate_plugin( + output_dir: Path, + version: str, + output_level: str = "standard", +) -> None: + """Generate an Agent Plugins v1.0.0 directory. + + Writes plugin.json, mcp.json, and skills/code-context/SKILL.md to + ``output_dir``. Safe to call repeatedly (overwrites existing files). + """ + output_dir.mkdir(parents=True, exist_ok=True) + + # plugin.json + plugin_manifest = { + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "code-context-engine", + "version": version, + "description": ( + "Index your codebase. AI searches instead of re-reading files. " + "94% token savings." + ), + "author": { + "name": "Elara Labs", + "url": "https://github.com/elara-labs/code-context-engine", + }, + "repository": "https://github.com/elara-labs/code-context-engine", + "license": "MIT", + "keywords": [ + "code-search", + "token-savings", + "mcp", + "code-indexing", + "retrieval", + ], + } + (output_dir / "plugin.json").write_text( + json.dumps(plugin_manifest, indent=2) + "\n", encoding="utf-8" + ) + + # mcp.json + mcp_config = { + "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", + "mcpServers": { + "code-context-engine": { + "type": "stdio", + "command": "uvx", + "args": [ + "--from", + "code-context-engine[local]", + "cce", + "serve", + ], + }, + }, + } + (output_dir / "mcp.json").write_text( + json.dumps(mcp_config, indent=2) + "\n", encoding="utf-8" + ) + + # skills/code-context/SKILL.md + skill_dir = output_dir / "skills" / "code-context" + skill_dir.mkdir(parents=True, exist_ok=True) + + description = ( + "Intelligent code retrieval and cross-session memory for AI coding " + "agents. Use when searching codebases, answering questions about " + "code, exploring architecture, finding functions or patterns, or " + "recalling past decisions. Provides context_search, expand_chunk, " + "related_context, session_recall, record_decision, record_code_area, " + "and set_output_compression MCP tools." + ) + frontmatter = ( + "---\n" + "name: code-context\n" + f"description: >\n" + + "".join(f" {line}\n" for line in description.splitlines()) + + "license: MIT\n" + "compatibility: Requires Python 3.11+ and uv (or uvx)\n" + "metadata:\n" + " author: elara-labs\n" + f' version: "{version}"\n' + "---\n\n" + ) + body = _build_instructions(output_level) + (skill_dir / "SKILL.md").write_text(frontmatter + body, encoding="utf-8") + + # skills/code-context/references/tools.md + ref_dir = skill_dir / "references" + ref_dir.mkdir(parents=True, exist_ok=True) + tools_ref = (Path(__file__).parent / "data" / "tools_reference.md").read_text( + encoding="utf-8" + ) + (ref_dir / "tools.md").write_text(tools_ref, encoding="utf-8") + + # LICENSE (copy from package root if available) + pkg_license = Path(__file__).parent.parent.parent / "LICENSE" + if pkg_license.exists(): + (output_dir / "LICENSE").write_text( + pkg_license.read_text(encoding="utf-8"), encoding="utf-8" + ) diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 8ecd67f..e715bd6 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1,7 +1,10 @@ """Tests for Agent Plugins generation and instruction template.""" +import json from pathlib import Path -from context_engine.editors import get_instructions_base +import yaml + +from context_engine.editors import generate_plugin, get_instructions_base def test_instructions_base_loads_from_file(): @@ -18,3 +21,76 @@ def test_instructions_base_has_no_claude_specific_content(): text = get_instructions_base() assert "Claude Code" not in text assert "CLAUDE.md" not in text + + +def test_generate_plugin_creates_structure(tmp_path): + """generate_plugin writes the complete Agent Plugin directory.""" + out = tmp_path / "plugin" + generate_plugin(out, version="1.2.3") + + assert (out / "plugin.json").exists() + assert (out / "mcp.json").exists() + assert (out / "skills" / "code-context" / "SKILL.md").exists() + assert (out / "skills" / "code-context" / "references" / "tools.md").exists() + + +def test_plugin_json_schema(tmp_path): + """plugin.json must conform to Agent Plugins v1.0.0.""" + out = tmp_path / "plugin" + generate_plugin(out, version="1.2.3") + + data = json.loads((out / "plugin.json").read_text()) + assert data["$schema"] == "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json" + assert data["name"] == "code-context-engine" + assert data["version"] == "1.2.3" + assert data["license"] == "MIT" + + +def test_mcp_json_schema(tmp_path): + """mcp.json must declare stdio transport with uvx.""" + out = tmp_path / "plugin" + generate_plugin(out, version="1.2.3") + + data = json.loads((out / "mcp.json").read_text()) + assert data["$schema"] == "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json" + server = data["mcpServers"]["code-context-engine"] + assert server["type"] == "stdio" + assert server["command"] == "uvx" + assert "cce" in server["args"] + assert "serve" in server["args"] + + +def test_skill_md_frontmatter(tmp_path): + """SKILL.md must have valid Agent Skills frontmatter.""" + out = tmp_path / "plugin" + generate_plugin(out, version="1.2.3") + + content = (out / "skills" / "code-context" / "SKILL.md").read_text() + # Split frontmatter + assert content.startswith("---\n") + parts = content.split("---\n", 2) + fm = yaml.safe_load(parts[1]) + assert fm["name"] == "code-context" + assert len(fm["description"]) > 10 + assert fm["license"] == "MIT" + assert fm["metadata"]["version"] == "1.2.3" + + +def test_skill_md_body_has_instructions(tmp_path): + """SKILL.md body must contain the instruction template content.""" + out = tmp_path / "plugin" + generate_plugin(out, version="1.2.3") + + content = (out / "skills" / "code-context" / "SKILL.md").read_text() + assert "context_search" in content + assert "record_decision" in content + + +def test_generate_plugin_overwrites_existing(tmp_path): + """Regenerating into the same directory overwrites cleanly.""" + out = tmp_path / "plugin" + generate_plugin(out, version="1.0.0") + generate_plugin(out, version="2.0.0") + + data = json.loads((out / "plugin.json").read_text()) + assert data["version"] == "2.0.0" From a32fe3126343e7c701cd7eb756300eb6f6444163 Mon Sep 17 00:00:00 2001 From: rajkumarsakthivel Date: Fri, 7 Aug 2026 23:19:57 +0100 Subject: [PATCH 05/10] feat(serve): auto-discover project root from cwd walk-up --- src/context_engine/cli.py | 28 ++++++++++++++++++++++++ tests/test_plugin.py | 46 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/src/context_engine/cli.py b/src/context_engine/cli.py index b001520..bc9344b 100644 --- a/src/context_engine/cli.py +++ b/src/context_engine/cli.py @@ -23,6 +23,25 @@ from context_engine.utils import project_storage_dir +def _discover_project_root(start: Path) -> Path | None: + """Walk up from *start* looking for a project root marker. + + Checks each directory for .context-engine.yaml (explicit CCE project) + or .git/ (any git repo). Returns the first match, or None at the + filesystem root. + """ + current = start.resolve() + while True: + if (current / ".context-engine.yaml").exists(): + return current + if (current / ".git").exists(): + return current + parent = current.parent + if parent == current: + return None + current = parent + + def _safe_cwd() -> Path: """Return `Path.cwd()` or raise a `click.ClickException` with a friendly, actionable error if the OS denies access. @@ -2792,6 +2811,15 @@ def serve(ctx: click.Context, as_http: bool, host: str, port: int, project_dir: ctx.obj["config"] = load_config( project_path=target_config if target_config.exists() else None ) + else: + discovered = _discover_project_root(Path.cwd()) + if discovered and discovered != Path.cwd(): + import os + os.chdir(str(discovered)) + target_config = discovered / PROJECT_CONFIG_NAME + ctx.obj["config"] = load_config( + project_path=target_config if target_config.exists() else None + ) if as_http: from context_engine.serve_http import run_http_server run_http_server(ctx.obj["config"], host=host, port=port) diff --git a/tests/test_plugin.py b/tests/test_plugin.py index e715bd6..6602c98 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -4,6 +4,7 @@ import yaml +from context_engine.cli import _discover_project_root from context_engine.editors import generate_plugin, get_instructions_base @@ -94,3 +95,48 @@ def test_generate_plugin_overwrites_existing(tmp_path): data = json.loads((out / "plugin.json").read_text()) assert data["version"] == "2.0.0" + + +# ── Project auto-discovery tests ───────────────────────────────────── + + +def test_discover_project_root_finds_git(tmp_path): + """Walk-up finds .git directory.""" + project = tmp_path / "myproject" + project.mkdir() + (project / ".git").mkdir() + subdir = project / "src" / "deep" + subdir.mkdir(parents=True) + + assert _discover_project_root(subdir) == project + + +def test_discover_project_root_finds_context_engine_yaml(tmp_path): + """Walk-up finds .context-engine.yaml.""" + project = tmp_path / "myproject" + project.mkdir() + (project / ".context-engine.yaml").write_text("indexer:\n watch: true\n") + subdir = project / "src" + subdir.mkdir() + + assert _discover_project_root(subdir) == project + + +def test_discover_project_root_prefers_context_engine_yaml(tmp_path): + """When .context-engine.yaml is found first on walk-up, it wins.""" + root = tmp_path / "root" + root.mkdir() + (root / ".git").mkdir() + inner = root / "inner" + inner.mkdir() + (inner / ".context-engine.yaml").write_text("") + + assert _discover_project_root(inner) == inner + + +def test_discover_project_root_returns_none(tmp_path): + """Returns None when no project markers found.""" + bare = tmp_path / "bare" + bare.mkdir() + + assert _discover_project_root(bare) is None From 72137f42b6c119b9e44aa37ccb58212f4ca06887 Mon Sep 17 00:00:00 2001 From: rajkumarsakthivel Date: Fri, 7 Aug 2026 23:22:56 +0100 Subject: [PATCH 06/10] feat: add --plugin flag to cce init for Agent Plugins v1.0.0 Generate a portable Agent Plugin directory (plugin.json, mcp.json, SKILL.md) compatible with VS Code, Cursor, Copilot, Codex, ChatGPT, and Kiro. Uses uvx to launch CCE on demand. --- README.md | 5 ++++ docs-src/src/content/docs/cli-reference.md | 12 ++++++++++ docs-src/src/content/docs/getting-started.md | 9 +++++++ docs/wiki/CLI-Reference.md | 16 +++++++++++++ src/context_engine/cli.py | 25 +++++++++++++++++++- 5 files changed, 66 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e60db6..8f3a6c7 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,10 @@ cce init Restart your editor. Done. Every question now hits the index instead of re-reading files. +> **Agent Plugin support:** Run `cce init --plugin` to generate a portable +> [Agent Plugin](https://agent-plugins.org) directory that works with +> VS Code, Cursor, Copilot, Codex, ChatGPT, and Kiro. + > **Already have Ollama?** Skip `[local]` and use `uv tool install code-context-engine` instead. CCE auto-detects Ollama at localhost:11434 and uses `nomic-embed-text`.
@@ -359,6 +363,7 @@ CCE's cross-session memory depends on the agent calling `record_decision` and `r ```bash cce init # Index + install hooks + register MCP +cce init --plugin # Generate Agent Plugin for VS Code, Cursor, etc. cce # Status banner cce savings # Token savings with dollar estimates cce savings --all # All projects diff --git a/docs-src/src/content/docs/cli-reference.md b/docs-src/src/content/docs/cli-reference.md index 0b1e445..a7f4d99 100644 --- a/docs-src/src/content/docs/cli-reference.md +++ b/docs-src/src/content/docs/cli-reference.md @@ -25,6 +25,18 @@ What it does: - Creates or updates agent instruction files. - Adds per-machine files to `.gitignore`. +**Agent Plugin flags:** + +| Flag | Description | +|------|-------------| +| `--plugin` | Generate an [Agent Plugin](https://agent-plugins.org) directory alongside the MCP config | +| `--plugin-dir ` | Output directory for the plugin (default: `.cce/plugin/`) | + +```bash +cce init --plugin +cce init --plugin --plugin-dir ./my-plugin +``` + ## cce index Re-index files that have changed since the last run. diff --git a/docs-src/src/content/docs/getting-started.md b/docs-src/src/content/docs/getting-started.md index 7100179..24be394 100644 --- a/docs-src/src/content/docs/getting-started.md +++ b/docs-src/src/content/docs/getting-started.md @@ -58,6 +58,15 @@ cce init --agent pi # Pi only cce init --agent all # Every supported editor ``` +### Agent Plugin + +Add `--plugin` to generate a portable [Agent Plugin](https://agent-plugins.org) directory. Editors that support the Agent Plugin specification (VS Code, Cursor, Copilot, Codex, ChatGPT, Kiro) will discover and load it automatically. + +```bash +cce init --plugin # Plugin written to .cce/plugin/ +cce init --plugin --plugin-dir ./my-plugin # Custom output directory +``` + ## Verify it works Restart your editor, then ask a question about your code. The agent will call `context_search` via MCP instead of reading files. diff --git a/docs/wiki/CLI-Reference.md b/docs/wiki/CLI-Reference.md index 94e9912..f780487 100644 --- a/docs/wiki/CLI-Reference.md +++ b/docs/wiki/CLI-Reference.md @@ -169,6 +169,22 @@ target a specific integration instead of auto-detection. - Creates or updates agent instruction files (`CLAUDE.md`, `AGENTS.md`, or `.github/copilot-instructions.md`) - Adds per-machine files to `.gitignore` +### Agent Plugin generation + +Use `--plugin` to generate a portable [Agent Plugin](https://agent-plugins.org) directory alongside the standard MCP config. The plugin directory can be loaded by VS Code, Cursor, Copilot, Codex, ChatGPT, and Kiro without any additional configuration. + +```bash +cce init --plugin # Generate plugin in default location (.cce/plugin/) +cce init --plugin --plugin-dir ./my-plugin # Generate plugin in a custom directory +``` + +| Flag | Description | +|------|-------------| +| `--plugin` | Generate an Agent Plugin directory in addition to MCP config | +| `--plugin-dir ` | Output directory for the plugin (default: `.cce/plugin/`) | + +The generated directory contains a manifest and tool definitions that conform to the [Agent Plugin specification](https://agent-plugins.org). Editors that support Agent Plugins will discover and load it automatically. + --- ## cce index diff --git a/src/context_engine/cli.py b/src/context_engine/cli.py index bc9344b..a089258 100644 --- a/src/context_engine/cli.py +++ b/src/context_engine/cli.py @@ -897,8 +897,10 @@ def _init_instruction_targets(editor_targets: set[str]) -> set[str]: show_default=True, help="Agent/editor target: auto, claude, codex, copilot, pi, or all.", ) +@click.option("--plugin", "gen_plugin", is_flag=True, help="Generate an Agent Plugin directory") +@click.option("--plugin-dir", default=None, type=click.Path(), help="Plugin output directory (default: .cce/plugin/)") @click.pass_context -def init(ctx: click.Context, agent: str) -> None: +def init(ctx: click.Context, agent: str, gen_plugin: bool, plugin_dir: str | None) -> None: """Initialize context engine and connect it to AI coding agents.""" from context_engine.indexer.git_hooks import install_hooks from context_engine.project_commands import ensure_gitignore @@ -2754,6 +2756,27 @@ def upgrade(ctx: click.Context, check: bool) -> None: install_hooks(str(project_dir)) _ok("Git hooks refreshed") + # Agent Plugin generation + if gen_plugin: + from context_engine.editors import generate_plugin + from importlib.metadata import version as pkg_version + try: + ver = pkg_version("code-context-engine") + except Exception: + ver = "0.0.0" + plugin_out = Path(plugin_dir) if plugin_dir else project_dir / ".cce" / "plugin" + generate_plugin(plugin_out, version=ver, output_level=output_level) + _ok("Agent Plugin generated at " + click.style(str(plugin_out), fg="cyan")) + # Add default plugin path to .gitignore + if not plugin_dir: + gitignore = project_dir / ".gitignore" + gi_content = gitignore.read_text(encoding="utf-8") if gitignore.exists() else "" + if ".cce/plugin/" not in gi_content: + gitignore.write_text( + gi_content.rstrip() + "\n\n# CCE Agent Plugin (generated)\n.cce/plugin/\n", + encoding="utf-8", + ) + click.echo("") click.echo( click.style(" Done!", fg="green", bold=True) + From b29847cec874b242767e88ef31b72ab15cd9e507 Mon Sep 17 00:00:00 2001 From: rajkumarsakthivel Date: Fri, 7 Aug 2026 23:28:48 +0100 Subject: [PATCH 07/10] docs: expand Agent Plugin coverage across README, wiki, and Starlight docs --- README.md | 45 +++++++++++++++++++- docs-src/src/content/docs/agents/overview.md | 14 ++++++ docs-src/src/content/docs/cli-reference.md | 9 +++- docs-src/src/content/docs/getting-started.md | 11 ++++- docs-src/src/content/docs/introduction.md | 2 + docs/wiki/CLI-Reference.md | 6 +++ 6 files changed, 83 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8f3a6c7..78e131f 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,9 @@ Restart your editor. Done. Every question now hits the index instead of re-readi > **Agent Plugin support:** Run `cce init --plugin` to generate a portable > [Agent Plugin](https://agent-plugins.org) directory that works with -> VS Code, Cursor, Copilot, Codex, ChatGPT, and Kiro. +> VS Code, Cursor, Copilot, Codex, ChatGPT, and Kiro. The plugin uses +> `uvx` to launch CCE on demand, so users don't need to pre-install the +> Python package. See [Agent Plugin](#agent-plugin) below. > **Already have Ollama?** Skip `[local]` and use `uv tool install code-context-engine` instead. CCE auto-detects Ollama at localhost:11434 and uses `nomic-embed-text`. @@ -359,6 +361,47 @@ CCE's cross-session memory depends on the agent calling `record_decision` and `r --- +## Agent Plugin + +[Agent Plugins](https://agent-plugins.org) is an open standard (v1.0.0) backed by Amazon, Cursor, Microsoft, OpenAI, and Vercel for packaging AI skills and MCP servers into portable, zero-install bundles. CCE can generate a plugin directory that compatible editors discover and load automatically. + +```bash +cce init --plugin # Generate at .cce/plugin/ +cce init --plugin --plugin-dir ~/plugins/cce # Custom location +cce init --agent claude --plugin # Both: agent config + plugin +``` + +### What gets generated + +``` +.cce/plugin/ +├── plugin.json # Agent Plugins v1.0.0 manifest +├── mcp.json # MCP server config (uvx + stdio) +├── skills/ +│ └── code-context/ +│ ├── SKILL.md # Agent instructions (frontmatter + body) +│ └── references/ +│ └── tools.md # Per-tool parameter docs (loaded on demand) +└── LICENSE +``` + +### Compatible editors + +VS Code, GitHub Copilot, ChatGPT, Codex, Cursor, and Kiro. The plugin uses `uvx` to launch CCE on demand, so users do not need to pre-install the Python package. The MCP server auto-discovers the project root by walking up from its working directory, looking for `.context-engine.yaml` or `.git/`. + +### When to use `--plugin` vs `--agent` + +| | `--agent` (default) | `--plugin` | +|---|---|---| +| Install method | Writes editor-specific config files | Generates a portable plugin directory | +| Zero-install | No, CCE must be on PATH | Yes, `uvx` fetches CCE on demand | +| Instruction updates | Stale until `cce init` re-run | Stale until `cce init --plugin` re-run | +| Best for | Your own machine | Sharing with a team or distributing | + +Both can be used together. `--agent` handles per-editor MCP config, `--plugin` provides a portable alternative. + +--- + ## CLI at a glance ```bash diff --git a/docs-src/src/content/docs/agents/overview.md b/docs-src/src/content/docs/agents/overview.md index d4ffe01..6bfde4a 100644 --- a/docs-src/src/content/docs/agents/overview.md +++ b/docs-src/src/content/docs/agents/overview.md @@ -59,6 +59,20 @@ Or configure everything at once: cce init --agent all ``` +## Agent Plugin (alternative install) + +Instead of per-editor MCP config, you can generate a portable [Agent Plugin](https://agent-plugins.org) directory: + +```bash +cce init --plugin +``` + +This creates a `.cce/plugin/` directory containing a manifest, MCP server config, and skill instructions that conform to the Agent Plugins v1.0.0 specification. Editors that support Agent Plugins (VS Code, Cursor, Copilot, Codex, ChatGPT, Kiro) discover and load it automatically. + +The plugin uses `uvx` to fetch and run CCE on demand, so team members who install the plugin do not need CCE pre-installed. Both `--agent` and `--plugin` can be used together in the same `cce init` command. + +See the [Getting Started](/code-context-engine/guide/getting-started/#agent-plugin) page for more details. + ## Common issues across all agents ### "cce: command not found" diff --git a/docs-src/src/content/docs/cli-reference.md b/docs-src/src/content/docs/cli-reference.md index a7f4d99..91d9f6e 100644 --- a/docs-src/src/content/docs/cli-reference.md +++ b/docs-src/src/content/docs/cli-reference.md @@ -33,10 +33,13 @@ What it does: | `--plugin-dir ` | Output directory for the plugin (default: `.cce/plugin/`) | ```bash -cce init --plugin -cce init --plugin --plugin-dir ./my-plugin +cce init --plugin # Generate at .cce/plugin/ +cce init --plugin --plugin-dir ~/plugins/ # Custom output directory +cce init --agent claude --plugin # Both: agent config + plugin ``` +The generated directory contains `plugin.json`, `mcp.json`, and `skills/code-context/SKILL.md` conforming to the [Agent Plugins v1.0.0](https://agent-plugins.org) specification. Compatible editors (VS Code, Cursor, Copilot, Codex, ChatGPT, Kiro) discover and load the plugin automatically. The plugin uses `uvx` to launch CCE on demand, so pre-installing the Python package is not required. The default `.cce/plugin/` path is added to `.gitignore` automatically. + ## cce index Re-index files that have changed since the last run. @@ -182,6 +185,8 @@ cce serve cce serve --project-dir /path/to/project ``` +When `--project-dir` is not provided, `cce serve` auto-discovers the project root by walking up from the current working directory, looking for `.context-engine.yaml` or `.git/`. This allows the MCP server to work correctly when launched from subdirectories or from an Agent Plugin directory. + ## cce list Show every available command grouped by category. diff --git a/docs-src/src/content/docs/getting-started.md b/docs-src/src/content/docs/getting-started.md index 24be394..1d374ff 100644 --- a/docs-src/src/content/docs/getting-started.md +++ b/docs-src/src/content/docs/getting-started.md @@ -60,13 +60,22 @@ cce init --agent all # Every supported editor ### Agent Plugin -Add `--plugin` to generate a portable [Agent Plugin](https://agent-plugins.org) directory. Editors that support the Agent Plugin specification (VS Code, Cursor, Copilot, Codex, ChatGPT, Kiro) will discover and load it automatically. +Add `--plugin` to generate a portable [Agent Plugin](https://agent-plugins.org) directory. Agent Plugins is an open standard (v1.0.0) for packaging AI skills and MCP servers into zero-install bundles. ```bash cce init --plugin # Plugin written to .cce/plugin/ cce init --plugin --plugin-dir ./my-plugin # Custom output directory +cce init --agent claude --plugin # Both: agent config + plugin ``` +The generated plugin contains a manifest (`plugin.json`), MCP server config (`mcp.json`), and a skill file (`SKILL.md`) with CCE instructions. Compatible editors (VS Code, Cursor, GitHub Copilot, Codex, ChatGPT, Kiro) discover and load it automatically. + +The plugin uses `uvx` to launch CCE on demand, so users who install the plugin do not need to pre-install the Python package. The `--plugin` flag is independent of `--agent` and both can be used together. + +:::tip +After upgrading CCE, re-run `cce init --plugin` to regenerate the plugin with the latest version and instructions. +::: + ## Verify it works Restart your editor, then ask a question about your code. The agent will call `context_search` via MCP instead of reading files. diff --git a/docs-src/src/content/docs/introduction.md b/docs-src/src/content/docs/introduction.md index 7f8d168..b15a929 100644 --- a/docs-src/src/content/docs/introduction.md +++ b/docs-src/src/content/docs/introduction.md @@ -41,6 +41,8 @@ CCE parses your code into semantic chunks (functions, classes, modules) using Tr | OpenCode | `opencode.json` | | | Tabnine | `.tabnine/agent/settings.json` | `TABNINE.md` | +CCE also supports [Agent Plugins](https://agent-plugins.org) (`cce init --plugin`), a portable zero-install alternative that works across VS Code, Cursor, Copilot, Codex, ChatGPT, and Kiro without per-editor config files. + ## How it works 1. **Index** — Tree-sitter parses code into semantic chunks. Stored locally with vector embeddings. diff --git a/docs/wiki/CLI-Reference.md b/docs/wiki/CLI-Reference.md index f780487..e134b73 100644 --- a/docs/wiki/CLI-Reference.md +++ b/docs/wiki/CLI-Reference.md @@ -765,4 +765,10 @@ cce serve --project-dir /path/to/your/project cce serve --http ``` +### Project auto-discovery + +When `--project-dir` is not provided, `cce serve` walks up from the current working directory to find the project root. It checks each directory for `.context-engine.yaml` (explicit CCE project marker) or `.git/` (any git repo). This means the MCP server works correctly when launched from a subdirectory or from an Agent Plugin directory inside the project. + +### HTTP endpoint + When `--http` is passed, the server also listens on an HTTP port and exposes a `POST /search` endpoint. This is useful for custom agent integrations that speak HTTP instead of MCP stdio. The endpoint accepts JSON with `query`, `top_k` (1..100), and `confidence_threshold` (0.0..1.0), and returns ranked results with confidence scores. From a3349680ca3b481a8143f0b83cde0e7b69060722 Mon Sep 17 00:00:00 2001 From: rajkumarsakthivel Date: Fri, 7 Aug 2026 23:32:56 +0100 Subject: [PATCH 08/10] docs(site): add Agent Plugin to landing page hero, editors, features, and comparison --- docs/index.html | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/docs/index.html b/docs/index.html index d299d5a..3f73298 100644 --- a/docs/index.html +++ b/docs/index.html @@ -9,7 +9,7 @@ - +