diff --git a/README.md b/README.md index cb49da2c..8780c97e 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,7 @@ innovation-lab-examples/ |---------|-------------|------------|------------| | [contributors/community_agent](contributors/community_agent/) | AI community growth agent for events and hackathons | Python, uAgents, ASI:One, Tavily | 🟡 Intermediate | | [contributors/news-summarizer-agent](contributors/news-summarizer-agent/) | Fetches top headlines for a topic via NewsAPI and summarizes them with ASI:One, via Chat Protocol | Python, uAgents, NewsAPI, ASI:One | 🟡 Intermediate | +| contributors/workspace-context-provider | Hybrid AI workspace context provider that indexes local codebases and exposes them through a Streamlit RAG interface and an MCP server for IDE integration | Python, Streamlit, FastMCP, ChromaDB, Sentence Transformers, Watchdog, ASI API | 🟡 Intermediate | ### 🌐 Web3 & Blockchain diff --git a/contributors/CHANGELOG.md b/contributors/CHANGELOG.md index ef54087b..b8d6e289 100644 --- a/contributors/CHANGELOG.md +++ b/contributors/CHANGELOG.md @@ -12,3 +12,4 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - `contributors/` folder and contribution guide for community agent examples - `contributors/community_agent/` — moved from repository root; AI community growth agent for events and hackathons - `contributors/news-summarizer-agent/` — beginner-friendly agent that fetches top headlines via NewsAPI and summarizes them with ASI:One; now a uAgent with Chat Protocol support +- `contributors/workspace-context-provider/` : Hybrid AI workspace context provider that indexes local codebases and exposes them through a Streamlit RAG interface and an MCP server for IDE integration \ No newline at end of file diff --git a/contributors/workspace-context-provider/README.md b/contributors/workspace-context-provider/README.md new file mode 100644 index 00000000..c7c8f7a2 --- /dev/null +++ b/contributors/workspace-context-provider/README.md @@ -0,0 +1,232 @@ +# Autonomous Workspace Context Provider + +A hybrid AI context provider that continuously indexes your local workspace and exposes it through a Streamlit web interface and an MCP server for AI-powered code understanding. + +--- + +# 1. Overview + +The **Autonomous Workspace Context Provider** is a local Retrieval-Augmented Generation (RAG) agent that monitors your workspace, embeds your source code and documentation into a persistent vector database, and provides contextual information through two interfaces: + +* **Streamlit Web UI** for chatting with your codebase using the **ASI**** LLM API** +* **Model Context Protocol (MCP) Server** for integrating your local RAG pipeline with AI coding assistants such as VS Code (Cline/Roo Code), cursor, claude Desktop etc. + +### Category + +MCP, RAG, Tooling, Frontend + +### Tech Stack + +* Python +* Streamlit +* FastMCP +* ChromaDB +* Sentence Transformers +* Watchdog +* ASI API + + +--- + +# 2. Features + +* 🔄 Real-time workspace indexing using **watchdog** +* 🧠 Local embeddings with **all-MiniLM-L6-v2** +* 💾 Persistent vector storage using **ChromaDB** +* 🤖 Chat with your codebase through **ASI** +* 🔌 MCP server for IDE integration +* ⚡ Automatic vector database updates whenever files change + +--- + +# 3. Prerequisites + +* Python 3.10+ +* pip +* ASI API Key *(optional, required only for Streamlit chat)* + +--- + +# 4. Installation + +```bash +cd contributors/workspace-context-provider + +python -m venv venv + +# Windows +venv\Scripts\activate + +# macOS/Linux +source venv/bin/activate + +pip install -r requirements.txt +``` + +--- + +# 5. Environment Variables + +Create a `.env` file. + +```bash +cp .env.example .env +``` + +Example `.env.example` + +```env +# Required only for Streamlit AI chat +ASI1_API_KEY=your_asi1_api_key_here + +# Local directory to monitor and index +WORKSPACE_DIR=./target_workspace +``` + +### Variables + +| Variable | Description | +| --------------- | ----------------------------------------------------------------- | +| `ASI1_API_KEY` | Optional. Used for ASI LLM requests in the Streamlit application. | +| `WORKSPACE_DIR` | Directory that will be monitored and indexed into ChromaDB. | + +--- + +# 6. Run the Agent + +## Streamlit Web UI + +```bash +streamlit run app.py +``` + +The application will automatically begin monitoring the directory specified by `WORKSPACE_DIR`. + +--- + +## MCP Server + +Configure your IDE (Cline/Roo Code) by adding the following configuration to `cline_mcp_settings.json`. + +```json +{ + "mcpServers": { + "workspace-rag-provider": { + "command": "C:/path/to/venv/Scripts/python.exe", + "args": ["-m", "src.mcp_server"], + "cwd": "C:/path/to/contributors/workspace-context-provider", + "env": { + "WORKSPACE_DIR": "./target_workspace" + } + } + } +} +``` + +> **Note:** Replace the `command` and `cwd` values with the absolute paths on your machine. + +--- + +# 7. Expected Output + +After running the project: + +* ✅ Workspace monitoring starts successfully +* ✅ Modified files are automatically indexed +* ✅ Embeddings are stored in ChromaDB +* ✅ Streamlit UI answers questions about your codebase +* ✅ MCP server connects successfully to your IDE +* ✅ AI assistants can retrieve relevant workspace context + +--- + +# 8. Demo + +Add screenshots or GIFs demonstrating the project. + +```markdown +![Workspace Context Provider Demo](./assets/demo.png) +``` + +--- + + + +# 9. Architecture + +``` + Workspace Files + │ + ▼ + Watchdog Observer + │ + File Change Events + │ + ▼ + Sentence Transformer Embeddings + (all-MiniLM-L6-v2 Model) + │ + ▼ + ChromaDB + (Persistent Vector Store) + ▲ ▲ + │ │ + │ │ + Streamlit UI FastMCP Server + │ │ + ▼ ▼ + ASI:One API VS Code / Cursor / claude desktop +``` + +--- + +# 10. Troubleshooting + +### Missing `ASI1_API_KEY` + +If using the Streamlit chat interface, ensure the API key is present in your `.env` file. + +--- + +### Workspace not being indexed + +* Verify that `WORKSPACE_DIR` exists. +* Ensure the application has permission to access the directory. + +--- + +### MCP server not connecting + +* Check the absolute paths in `cline_mcp_settings.json`. +* Restart your IDE after updating the MCP configuration. + +--- + +### Dependency issues + +Recreate the virtual environment and reinstall dependencies. + +```bash +rm -rf venv + +python -m venv venv + +pip install -r requirements.txt +``` + +--- + +# 12. License + +This project follows the license of the parent repository unless stated otherwise. + +--- + +# ✅ Quick Checklist Before PR + +* [x] README updated using the repository template +* [x] `.env.example` added +* [ ] Demo image/GIF added under `assets/` +* [ ] Agent profile link included (if available) +* [ ] `ruff check .` passed +* [ ] `ruff format .` applied diff --git a/contributors/workspace-context-provider/app.py b/contributors/workspace-context-provider/app.py new file mode 100644 index 00000000..3d2a539a --- /dev/null +++ b/contributors/workspace-context-provider/app.py @@ -0,0 +1,117 @@ +import streamlit as st +from openai import OpenAI +import os +from dotenv import load_dotenv +from src.rag_pipeline import LocalRagEngine +from src.watcher import start_directory_watcher + +# Load environment variables +load_dotenv() +ASI1_API_KEY = os.getenv("ASI1_API_KEY", "") +WORKSPACE_DIR = os.getenv("WORKSPACE_DIR", "./target_workspace") + + +# ========================================== +# 1. INITIALIZE RAG & WATCHER (Cached) +# ========================================== +@st.cache_resource +def init_rag_system(): + """Initializes the vector DB and background watcher only once per session.""" + engine = LocalRagEngine() + # Start the background file watcher thread + start_directory_watcher(engine, WORKSPACE_DIR) + return engine + + +rag_engine = init_rag_system() + +# ========================================== +# 2. INITIALIZE ASI:ONE API CLIENT +# ========================================== +# ASI:One uses OpenAI-compatible endpoints. +client = OpenAI( + api_key=ASI1_API_KEY, + base_url="https://api.asi1.ai/v1", # Standard ASI:One endpoint +) + +# ========================================== +# 3. STREAMLIT UI SETUP +# ========================================== +st.set_page_config(page_title="Workspace Context Agent", page_icon="🤖", layout="wide") +st.title("🤖 Autonomous Workspace Agent") +st.markdown("*Powered by ASI:One & Local RAG*") + +# Initialize chat history +if "messages" not in st.session_state: + st.session_state.messages = [] + +# Display chat history +for message in st.session_state.messages: + with st.chat_message(message["role"]): + st.markdown(message["content"]) + +# ========================================== +# 4. CHAT LOGIC & RAG INJECTION +# ========================================== +if prompt := st.chat_input("Ask about your codebase... (e.g., 'How does auth work?')"): + # Display user prompt + with st.chat_message("user"): + st.markdown(prompt) + st.session_state.messages.append({"role": "user", "content": prompt}) + + # Fetch context from our local RAG pipeline + with st.spinner("🔍 Scanning local workspace..."): + local_context = rag_engine.query_context(prompt, top_k=3) + + # Construct the grounded prompt for ASI:One + system_prompt = f""" + You are an expert developer AI assistant. + Use the following retrieved local workspace context to answer the user's question accurately. + If the answer isn't in the context, say so. + + LOCAL WORKSPACE CONTEXT: + {local_context} + """ + + # Display assistant response + with st.chat_message("assistant"): + message_placeholder = st.empty() + + if not ASI1_API_KEY: + st.error( + "⚠️ ASI1_API_KEY is missing in your .env file! Displaying retrieved raw context instead:" + ) + message_placeholder.code(local_context) + st.session_state.messages.append( + { + "role": "assistant", + "content": f"**Raw Context Retrieved:**\n```\n{local_context}\n```", + } + ) + else: + try: + # Call ASI:One API + stream = client.chat.completions.create( + model="asi1-mini", # Replace with specific ASI1 model if needed + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": prompt}, + ], + stream=True, + ) + + full_response = "" + for chunk in stream: + # SAFETY CHECK: Ensure choices list is not empty! + if chunk.choices and len(chunk.choices) > 0: + if chunk.choices[0].delta.content is not None: + full_response += chunk.choices[0].delta.content + message_placeholder.markdown(full_response + "▌") + + message_placeholder.markdown(full_response) + st.session_state.messages.append( + {"role": "assistant", "content": full_response} + ) + + except Exception as e: + st.error(f"API Error: {e}") diff --git a/contributors/workspace-context-provider/assets/Screenshot 2026-06-26 210908.png b/contributors/workspace-context-provider/assets/Screenshot 2026-06-26 210908.png new file mode 100644 index 00000000..5a5db981 Binary files /dev/null and b/contributors/workspace-context-provider/assets/Screenshot 2026-06-26 210908.png differ diff --git a/contributors/workspace-context-provider/assets/Screenshot 2026-06-26 211412.png b/contributors/workspace-context-provider/assets/Screenshot 2026-06-26 211412.png new file mode 100644 index 00000000..8b641a9b Binary files /dev/null and b/contributors/workspace-context-provider/assets/Screenshot 2026-06-26 211412.png differ diff --git a/contributors/workspace-context-provider/data/chroma/25bdb85a-4b9c-448a-bd76-09710c68fe9c/data_level0.bin b/contributors/workspace-context-provider/data/chroma/25bdb85a-4b9c-448a-bd76-09710c68fe9c/data_level0.bin new file mode 100644 index 00000000..333248a8 Binary files /dev/null and b/contributors/workspace-context-provider/data/chroma/25bdb85a-4b9c-448a-bd76-09710c68fe9c/data_level0.bin differ diff --git a/contributors/workspace-context-provider/data/chroma/25bdb85a-4b9c-448a-bd76-09710c68fe9c/header.bin b/contributors/workspace-context-provider/data/chroma/25bdb85a-4b9c-448a-bd76-09710c68fe9c/header.bin new file mode 100644 index 00000000..bb547926 Binary files /dev/null and b/contributors/workspace-context-provider/data/chroma/25bdb85a-4b9c-448a-bd76-09710c68fe9c/header.bin differ diff --git a/contributors/workspace-context-provider/data/chroma/25bdb85a-4b9c-448a-bd76-09710c68fe9c/length.bin b/contributors/workspace-context-provider/data/chroma/25bdb85a-4b9c-448a-bd76-09710c68fe9c/length.bin new file mode 100644 index 00000000..8790bbfe Binary files /dev/null and b/contributors/workspace-context-provider/data/chroma/25bdb85a-4b9c-448a-bd76-09710c68fe9c/length.bin differ diff --git a/contributors/workspace-context-provider/data/chroma/25bdb85a-4b9c-448a-bd76-09710c68fe9c/link_lists.bin b/contributors/workspace-context-provider/data/chroma/25bdb85a-4b9c-448a-bd76-09710c68fe9c/link_lists.bin new file mode 100644 index 00000000..e69de29b diff --git a/contributors/workspace-context-provider/data/chroma/chroma.sqlite3 b/contributors/workspace-context-provider/data/chroma/chroma.sqlite3 new file mode 100644 index 00000000..5623d1c3 Binary files /dev/null and b/contributors/workspace-context-provider/data/chroma/chroma.sqlite3 differ diff --git a/contributors/workspace-context-provider/requirements.txt b/contributors/workspace-context-provider/requirements.txt new file mode 100644 index 00000000..2c24103c --- /dev/null +++ b/contributors/workspace-context-provider/requirements.txt @@ -0,0 +1,7 @@ +uagents +chromadb +sentence-transformers +langchain-text-splitters +watchdog +streamlit +openai \ No newline at end of file diff --git a/contributors/workspace-context-provider/src/__init__.py b/contributors/workspace-context-provider/src/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/contributors/workspace-context-provider/src/mcp_tools.py b/contributors/workspace-context-provider/src/mcp_tools.py new file mode 100644 index 00000000..fb576cec --- /dev/null +++ b/contributors/workspace-context-provider/src/mcp_tools.py @@ -0,0 +1,38 @@ +from mcp.server.fastmcp import FastMCP +from src.rag_pipeline import LocalRagEngine + +# 1. Initialize the FastMCP server +# This acts as the bridge between your local RAG database and external LLMs like Claude. +mcp = FastMCP("Workspace Context Provider") + +# 2. Connect to the exact same RAG Engine our Fetch Agent uses +rag_engine = LocalRagEngine() + + +# 3. Define the MCP Tool +# The @mcp.tool() decorator tells external LLMs exactly what this function does +# and what arguments it takes, so the LLM can call it autonomously. +@mcp.tool() +def get_workspace_context(query: str, top_k: int = 3) -> str: + """ + Fetch highly relevant workspace and codebase context. + Use this tool when you need to understand the user's local code, documentation, + or architectural decisions. + + Args: + query: The semantic question to search the codebase for (e.g., "How does authentication work?") + top_k: Number of context chunks to return (default is 3). + """ + print(f"[MCP Server] External LLM requested context for: '{query}'") + + # Query our local ChromaDB + retrieved_context = rag_engine.query_context(query, top_k=top_k) + + return retrieved_context + + +if __name__ == "__main__": + print("[MCP Server] Starting stdio server for LLM clients...") + # MCP servers typically communicate via standard input/output (stdio) + # so they can be spawned directly as subprocesses by Claude Desktop or Cursor. + mcp.run_stdio_async() diff --git a/contributors/workspace-context-provider/src/rag_pipeline.py b/contributors/workspace-context-provider/src/rag_pipeline.py new file mode 100644 index 00000000..f993edd9 --- /dev/null +++ b/contributors/workspace-context-provider/src/rag_pipeline.py @@ -0,0 +1,122 @@ +import os +import chromadb +from chromadb.utils import embedding_functions +from langchain_text_splitters import RecursiveCharacterTextSplitter + + +class LocalRagEngine: + def __init__(self, persist_dir="./data/chroma"): + """Initializes the vector database and embedding models.""" + # Ensure the data directory exists + os.makedirs(persist_dir, exist_ok=True) + + print("[RAG Engine] Initializing ChromaDB and local embedding model...") + # Persistent client saves data to your disk so it survives restarts + self.client = chromadb.PersistentClient(path=persist_dir) + + # We use a lightweight, free, local embedding model from HuggingFace + self.embed_fn = embedding_functions.SentenceTransformerEmbeddingFunction( + model_name="all-MiniLM-L6-v2" + ) + + # Get or create our workspace collection + self.collection = self.client.get_or_create_collection( + name="workspace_context", embedding_function=self.embed_fn + ) + + # Intelligent text splitter to avoid breaking sentences/code blocks in half + self.splitter = RecursiveCharacterTextSplitter( + chunk_size=500, chunk_overlap=100, length_function=len + ) + print("[RAG Engine] Initialization complete.") + + def index_file(self, file_path: str): + """Reads a file, chunks it, and saves it to the vector database.""" + if not os.path.exists(file_path): + print(f"[RAG Engine] Warning: File not found - {file_path}") + return + + try: + # Handle different encodings (Windows vs Linux) safely + with open(file_path, "r", encoding="utf-8", errors="ignore") as f: + content = f.read() + + # 1. Purge old chunks for this file to prevent duplicates on edit + self.purge_file(file_path) + + if not content.strip(): + return # Skip empty files + + # 2. Split the content into chunks + chunks = self.splitter.split_text(content) + + documents = [] + metadatas = [] + ids = [] + + # 3. Format data for ChromaDB + for i, chunk in enumerate(chunks): + documents.append(chunk) + metadatas.append({"source": file_path, "chunk_index": i}) + ids.append(f"{file_path}_chunk_{i}") + + # 4. Upsert into database + if documents: + self.collection.add(documents=documents, metadatas=metadatas, ids=ids) + print(f"[RAG Engine] Indexed: {file_path} ({len(chunks)} chunks)") + + except Exception as e: + print(f"[RAG Engine] Error indexing {file_path}: {e}") + + def purge_file(self, file_path: str): + """Removes a file's chunks from the database.""" + try: + self.collection.delete(where={"source": file_path}) + except Exception: + pass # Fails silently if file wasn't in DB yet + + def query_context(self, query: str, top_k: int = 2) -> str: + """Searches the database for the most relevant file chunks.""" + results = self.collection.query(query_texts=[query], n_results=top_k) + + if not results or not results["documents"] or len(results["documents"][0]) == 0: + return "No matching workspace context found." + + # Format the retrieved chunks into a clean payload + context_blocks = [] + for doc, meta in zip(results["documents"][0], results["metadatas"][0]): + context_blocks.append(f"--- Source: {meta['source']} ---\n{doc}") + + return "\n\n".join(context_blocks) + + +# ========================================== +# TEST BLOCK +# ========================================== +# This will only run if you execute this file directly (not when imported by the agent) +if __name__ == "__main__": + # 1. Create a dummy test file + test_file = "test_config.md" + with open(test_file, "w", encoding="utf-8") as f: + f.write("""# Test Config +This is a simple test configuration file for demonstration purposes. +""") + + # 2. Initialize our Engine + engine = LocalRagEngine() + + # 3. Index the file + print("\n--- Indexing ---") + engine.index_file(test_file) + + # 4. Query the engine + print("\n--- Querying ---") + test_query = "What is the purpose of the test configuration file?" + print(f"Question: {test_query}\n") + + answer = engine.query_context(test_query) + print(f"Retrieved Context:\n{answer}") + + # Clean up the dummy file + if os.path.exists(test_file): + os.remove(test_file) diff --git a/contributors/workspace-context-provider/src/watcher.py b/contributors/workspace-context-provider/src/watcher.py new file mode 100644 index 00000000..d0036eee --- /dev/null +++ b/contributors/workspace-context-provider/src/watcher.py @@ -0,0 +1,117 @@ +import os +import sys +import time + +# Add the root directory to the python path so direct execution works +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler +from src.rag_pipeline import LocalRagEngine + + +class WorkspaceHandler(FileSystemEventHandler): + def __init__(self, rag_engine: LocalRagEngine, target_dir: str): + self.rag_engine = rag_engine + self.target_dir = target_dir + # Only parse common readable code and documentation files + self.valid_extensions = ( + ".py", + ".md", + ".js", + ".ts", + ".json", + ".txt", + ".java", + ".cpp", + ) + # Dictionary to track when files were last processed to avoid double-triggers + self.last_processed = {} + + def should_process(self, path: str) -> bool: + """Check if the file is a valid type and NOT in a hidden folder like .git""" + # Normalize the path to fix mixed Windows/Linux slashes + clean_path = os.path.normpath(path) + + # Check if any folder or file is hidden (starts with a dot, but isn't current/parent dir) + if any( + part.startswith(".") and part not in (".", "..") + for part in clean_path.split(os.sep) + ): + return False + + return clean_path.endswith(self.valid_extensions) + + def debounce(self, file_path: str, wait_time: float = 1.0) -> bool: + """Prevents multiple triggers for a single IDE save action.""" + current_time = time.time() + if file_path in self.last_processed: + if current_time - self.last_processed[file_path] < wait_time: + return False # Too soon, ignore this trigger + self.last_processed[file_path] = current_time + return True + + def on_modified(self, event): + if not event.is_directory and self.should_process(event.src_path): + if self.debounce(event.src_path): + time.sleep(0.2) # Small buffer to let the OS release the file lock + self.rag_engine.index_file(event.src_path) + + def on_created(self, event): + if not event.is_directory and self.should_process(event.src_path): + if self.debounce(event.src_path): + time.sleep(0.2) + self.rag_engine.index_file(event.src_path) + + def on_deleted(self, event): + if not event.is_directory and self.should_process(event.src_path): + self.rag_engine.purge_file(event.src_path) + print(f"[Watcher] File deleted. Purged from DB: {event.src_path}") + + +def start_directory_watcher(rag_engine: LocalRagEngine, target_dir: str) -> Observer: + """Bootstraps the directory and starts the background observer thread.""" + os.makedirs(target_dir, exist_ok=True) + + # 1. Run an initial bootstrap scan of existing files + print(f"\n[Watcher] Bootstrapping directory: {target_dir}") + handler = WorkspaceHandler(rag_engine, target_dir) + + for root, _, files in os.walk(target_dir): + for file in files: + full_path = os.path.join(root, file) + if handler.should_process(full_path): + rag_engine.index_file(full_path) + + # 2. Start listening for live changes + observer = Observer() + observer.schedule(handler, path=target_dir, recursive=True) + observer.start() + print(f"[Watcher] Actively tracking architectural changes in: {target_dir}\n") + return observer + + +# ========================================== +# TEST BLOCK +# ========================================== +if __name__ == "__main__": + # Test the watcher independently + test_dir = "./target_workspace" + engine = LocalRagEngine() + + # Start the background thread + observer = start_directory_watcher(engine, test_dir) + + print("Watcher is running! Try doing this:") + print(f"1. Open the '{test_dir}' folder.") + print("2. Create a new file (e.g., test.md) and type something.") + print("3. Save it and watch this terminal.") + print("Press CTRL+C to stop.\n") + + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + observer.stop() + print("\n[Watcher] Stopped.") + observer.join() diff --git a/contributors/workspace-context-provider/target_workspace/hello.md b/contributors/workspace-context-provider/target_workspace/hello.md new file mode 100644 index 00000000..13d366de --- /dev/null +++ b/contributors/workspace-context-provider/target_workspace/hello.md @@ -0,0 +1,6 @@ +hi i am venky and i amm saying hello to u all + +spotify is the music provider i use mostly to isten songs. + +ksjhkfbk hff hsh h h hk kkkkkkkk +nnnnnnnnnnnnnnnnnnnnnn \ No newline at end of file diff --git a/contributors/workspace-context-provider/target_workspace/nested folder/hello.md b/contributors/workspace-context-provider/target_workspace/nested folder/hello.md new file mode 100644 index 00000000..c48a6551 --- /dev/null +++ b/contributors/workspace-context-provider/target_workspace/nested folder/hello.md @@ -0,0 +1,3 @@ +this is a nested folder file + +hello i am venky here \ No newline at end of file diff --git a/contributors/workspace-context-provider/target_workspace/test.md b/contributors/workspace-context-provider/target_workspace/test.md new file mode 100644 index 00000000..935909c2 --- /dev/null +++ b/contributors/workspace-context-provider/target_workspace/test.md @@ -0,0 +1,5 @@ +bbbbbbbbbbbbbbbb + +hhhhhhhhhhhhhhhh + jjjjjjjjjjjjjjjjj + \ No newline at end of file diff --git a/contributors/workspace-context-provider/target_workspace/testt.md b/contributors/workspace-context-provider/target_workspace/testt.md new file mode 100644 index 00000000..82d14043 --- /dev/null +++ b/contributors/workspace-context-provider/target_workspace/testt.md @@ -0,0 +1,10 @@ +this is a test file + +kjfdghdhgkdhbfg +kbbgkbg +fhgbddg +fhgbbjdfhbgjhfbgjhfbkbghf +ffbgkkhhkhk kjhfjshfkj + + +dkdfhjhbfhbjhdbf \ No newline at end of file