From 256c20d07132686ea9f77735d86f4cbc35c0be73 Mon Sep 17 00:00:00 2001 From: kpj2006 <24ucs074@lnmiit.ac.in> Date: Sat, 25 Jul 2026 18:01:30 +0530 Subject: [PATCH 1/3] feat: add context and implement automated subtree synchronization workflow --- .github/workflows/sync-subtrees.yml | 34 ++++++++++ context.md | 20 ------ context.py | 48 ++++++++++++++ main.py | 22 +++---- repo_metadata.py | 14 +++++ scripts/update_subtrees.py | 98 +++++++++++++++++++++++++++++ 6 files changed, 201 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/sync-subtrees.yml delete mode 100644 context.md create mode 100644 context.py create mode 100644 repo_metadata.py create mode 100644 scripts/update_subtrees.py diff --git a/.github/workflows/sync-subtrees.yml b/.github/workflows/sync-subtrees.yml new file mode 100644 index 0000000..fad3f3e --- /dev/null +++ b/.github/workflows/sync-subtrees.yml @@ -0,0 +1,34 @@ +name: Synchronize Remote Repository Context + +on: + schedule: + # Run daily at 00:00 UTC + - cron: '0 0 * * *' + workflow_dispatch: + +jobs: + sync-context: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install httpx + + - name: Run subtree context sync script + run: | + python scripts/update_subtrees.py + + - name: Commit and push updated context files + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "chore(context): auto-sync client repo .agent context files" + file_pattern: "repos/**" diff --git a/context.md b/context.md deleted file mode 100644 index 061dcdd..0000000 --- a/context.md +++ /dev/null @@ -1,20 +0,0 @@ -# MiniChain Context - -MiniChain is a minimal, educational implementation of a Proof-of-Work (PoW) blockchain written in Python. It includes a P2P network layer, a mempool for transaction management, cryptographic transaction signing, state management with accounts, and a minimal sandboxed smart contract execution environment. - -## Architecture & Components - -The `minichain` package is composed of the following files, each responsible for a specific part of the blockchain: - -- **`__init__.py`**: Exports the primary classes and functions for the package, acting as the public API. -- **`block.py`**: Defines the `Block` class, which handles block structure, deterministic timestamps, Merkle root calculation for transactions, and serialization for mining and hashing. -- **`chain.py`**: Contains the `Blockchain` class which manages the chain of blocks, handles adding new blocks after validating their hashes, and executes transactions against a temporary state to ensure atomicity. -- **`contract.py`**: Implements `ContractMachine`, a minimal, sandboxed Python-based smart contract execution environment. It uses `multiprocessing`, resource limits, and AST validation to restrict available built-ins and prevent malicious code execution. -- **`mempool.py`**: Defines the `Mempool` class, which holds pending valid transactions before they are mined into a block. It handles duplicate prevention, max size limits, and deterministic sorting for block inclusion. -- **`p2p.py`**: Implements a lightweight TCP-based peer-to-peer network (`P2PNetwork`) using `asyncio`. It handles connecting to peers, listening for incoming connections, broadcasting blocks and transactions, and validating incoming JSON messages. -- **`persistence.py`**: Provides utility functions to `save` and `load` the blockchain and state to a local JSON file (`data.json`) atomically. It uses fsync and temp files to prevent corruption and verifies chain integrity on load. -- **`pow.py`**: Contains the Proof-of-Work mining logic (`mine_block` and `calculate_hash`), enforcing difficulty targets, nonces, limits, and optional timeout conditions. -- **`serialization.py`**: Provides helper functions (`canonical_json_dumps`, `canonical_json_bytes`, `canonical_json_hash`) for deterministic JSON serialization, crucial for consistent signature verification and hashing across environments. -- **`state.py`**: Defines the `State` class, which manages account balances, nonces, and contract storage. It evaluates and applies transactions via distinct branches: regular transfers, contract deployments, and contract calls. -- **`transaction.py`**: Defines the `Transaction` class, handling transaction structure, digital signatures using `nacl` (Ed25519), verification, deterministic transaction IDs, and serialization. -- **`validators.py`**: Provides simple validation utilities, such as `is_valid_receiver`, ensuring addresses correspond to standard hex lengths (40 or 64 characters). \ No newline at end of file diff --git a/context.py b/context.py new file mode 100644 index 0000000..7408886 --- /dev/null +++ b/context.py @@ -0,0 +1,48 @@ +import logging +from pathlib import Path + +logger = logging.getLogger("pr-dashboard.context") + + +def get_repo_dir(repo_name: str) -> Path | None: + """Find repository context directory inside repos/ or workspace.""" + bot_root = Path(__file__).resolve().parent + candidates = [ + bot_root / "repos" / repo_name, + bot_root / repo_name, + bot_root.parent / repo_name, + ] + for c in candidates: + if c.exists() and c.is_dir(): + return c + return None + + +def load_full_repo_context(repo_name: str) -> str: + """Dynamically scan and load ALL markdown files inside repos// without hardcoding file lists.""" + repo_dir = get_repo_dir(repo_name) + if not repo_dir: + logger.warning(f"Repository directory for '{repo_name}' not found.") + return f"=== REPOSITORY: {repo_name} ===" + + context_parts = [f"=== REPOSITORY: {repo_name} ==="] + loaded_files = 0 + + # Recursively scan for all .md files inside the target repository directory + for md_file in sorted(repo_dir.rglob("*.md")): + # Skip internal cache or git folders + if any(part.startswith(".") and part not in [".agent"] for part in md_file.parts): + continue + + rel_path = md_file.relative_to(repo_dir) + try: + with open(md_file, "r", encoding="utf-8") as f: + content = f.read().strip() + if content: + context_parts.append(f"--- {rel_path} ---\n{content}") + loaded_files += 1 + except Exception as e: + logger.error(f"Error reading context file {md_file}: {e}") + + logger.info(f"Loaded {loaded_files} markdown context files for '{repo_name}'") + return "\n\n".join(context_parts) diff --git a/main.py b/main.py index d8209ab..e19ef14 100644 --- a/main.py +++ b/main.py @@ -2,7 +2,7 @@ main.py — entry point Flow: - 1. Load context.md (repo context — what MiniChain is, what's already built) + 1. Load complete target repo context from repos// via context.py 2. Fetch all PRs + extract CodeRabbit walkthrough + changes only 3. One combined Ollama call → groups PRs by problem 4. Deep Ollama analysis per conflict group (all PRs in group together) @@ -11,7 +11,7 @@ Run: python main.py Requires: gh (authenticated), ollama running on localhost:11434 -Optional: context.md in same folder (drop it in when ready) +Context Sync: python scripts/update_subtrees.py (syncs target repo context into repos//) """ import os, time, webbrowser @@ -19,19 +19,10 @@ from ollama import check_ollama from grouping import resolve_groups from render import build_conflict_html, build_isolated_html +from context import load_full_repo_context -OUT_DIR = os.path.dirname(os.path.abspath(__file__)) -CONTEXT_FILE = os.path.join(OUT_DIR, "context.md") +OUT_DIR = os.path.dirname(os.path.abspath(__file__)) -def load_context(): - if os.path.exists(CONTEXT_FILE): - with open(CONTEXT_FILE, "r", encoding="utf-8") as f: - content = f.read().strip() - print(f" Loaded context.md ({len(content)} chars)") - return content - print(" No context.md found — running without repo context") - print(" (Drop context.md in the same folder to enable it)") - return "" def main(): if not check_gh_auth(): @@ -42,8 +33,9 @@ def main(): print("ERROR: Ollama not reachable at localhost:11434") return - print(f"\nLoading repo context...") - repo_context = load_context() + target_repo_name = REPO.split("/")[-1] + print(f"\nLoading full repo context for '{target_repo_name}'...") + repo_context = load_full_repo_context(target_repo_name) print(f"\nFetching PRs for {REPO}...") raw_prs = fetch_prs() diff --git a/repo_metadata.py b/repo_metadata.py new file mode 100644 index 0000000..0de7751 --- /dev/null +++ b/repo_metadata.py @@ -0,0 +1,14 @@ +REPO_METADATA = { + "SocialShareButton": { + "url": "https://github.com/kpj2006/SocialShareButton/tree/matt-skills", + }, + "OrgExplorer": { + "url": "https://github.com/kpj2006/OrgExplorer/tree/skills", + }, + "GSoC-Proposal-Assistant": { + "url": "https://github.com/kpj2006/GSoC-Proposal-Assistant", + }, + "Template-Repo-Main": { + "url": "https://github.com/AOSSIE-Org/Template-Repo", + }, +} diff --git a/scripts/update_subtrees.py b/scripts/update_subtrees.py new file mode 100644 index 0000000..eb98746 --- /dev/null +++ b/scripts/update_subtrees.py @@ -0,0 +1,98 @@ +import os +import re +import sys +import logging +import httpx +from pathlib import Path + +# Add parent directory to sys.path to import repo_metadata +script_dir = Path(__file__).resolve().parent +bot_root = script_dir.parent +sys.path.insert(0, str(bot_root)) + +from repo_metadata import REPO_METADATA + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger("subtree-sync") + +# Key context files to sync from remote client repositories +KNOWN_CONTEXT_FILES = [ + ".agent/info/operational-data.md", + ".agent/core/architecture.md", + ".agent/core/code-mapping.md", + ".agent/core/edge-cases.md", + ".agent/core/examples.md", + ".agent/instructions/setup.md", + ".agent/instructions/testing.md", + ".agent/instructions/deployment.md", + ".agent/instructions/ci-cd.md", + ".agent/instructions/format-guide.md", + ".agent/instructions/checklist.md", + ".agent/instructions/bad-patterns.md", + "references/checklist.md", + "references/format-guide.md", + "references/bad-patterns.md", + "SKILL.md", + "AGENTS.md", + "README.md", +] + + +def parse_github_url(url: str) -> tuple[str, str, str]: + """Parse owner, repo, and ref from GitHub URL.""" + match = re.match(r"https://github\.com/([^/]+)/([^/]+)(?:/tree/([^/]+))?", url) + if match: + owner, repo, ref = match.groups() + repo = repo.removesuffix(".git") + ref = ref or "main" + return owner, repo, ref + return "", "", "main" + + +def sync_repo_context(repo_name: str, meta: dict, client: httpx.Client): + """Fetch specified .agent context files directly from raw GitHub endpoint.""" + url = meta.get("url") + if not url: + logger.warning(f"No URL defined for {repo_name}, skipping.") + return + + owner, repo, ref = parse_github_url(url) + if not owner or not repo: + logger.error(f"Invalid GitHub URL for {repo_name}: {url}") + return + + logger.info(f"Syncing context for '{repo_name}' ({owner}/{repo}@{ref})...") + target_dir = bot_root / "repos" / repo_name + target_dir.mkdir(parents=True, exist_ok=True) + + headers = {"User-Agent": "PullRequestDashboard-Context-Sync"} + downloaded_count = 0 + + for rel_file in KNOWN_CONTEXT_FILES: + raw_url = f"https://raw.githubusercontent.com/{owner}/{repo}/{ref}/{rel_file}" + try: + res = client.get(raw_url, headers=headers) + if res.status_code == 200: + dest_path = target_dir / rel_file + dest_path.parent.mkdir(parents=True, exist_ok=True) + dest_path.write_bytes(res.content) + logger.info(f"Downloaded: repos/{repo_name}/{rel_file}") + downloaded_count += 1 + except Exception as e: + logger.debug(f"Failed downloading {rel_file} for {repo_name}: {e}") + + logger.info(f"Synced {downloaded_count} context files into repos/{repo_name}") + + +def sync_all_subtrees(): + os.chdir(bot_root) + repos_dir = bot_root / "repos" + repos_dir.mkdir(exist_ok=True) + + with httpx.Client(timeout=20.0, follow_redirects=True) as client: + for repo_name, meta in REPO_METADATA.items(): + sync_repo_context(repo_name, meta, client) + + +if __name__ == "__main__": + sync_all_subtrees() From 26d960703ed60560460b4ece6852362982fba7ff Mon Sep 17 00:00:00 2001 From: kpj2006 <24ucs074@lnmiit.ac.in> Date: Wed, 12 Aug 2026 20:51:39 +0530 Subject: [PATCH 2/3] Update workflow and context handling: enhance sync process and limit context size --- .github/workflows/sync-subtrees.yml | 24 ++++++++++++++--- context.py | 41 ++++++++++++++++++++++++----- repo_metadata.py | 2 +- 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/.github/workflows/sync-subtrees.yml b/.github/workflows/sync-subtrees.yml index fad3f3e..c3b5227 100644 --- a/.github/workflows/sync-subtrees.yml +++ b/.github/workflows/sync-subtrees.yml @@ -6,29 +6,45 @@ on: - cron: '0 0 * * *' workflow_dispatch: +concurrency: + group: sync-subtrees + cancel-in-progress: false + +permissions: + contents: write + jobs: sync-context: + name: Sync repository context runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: '3.11' - name: Install dependencies run: | python -m pip install --upgrade pip - pip install httpx + pip install httpx==0.28.1 - name: Run subtree context sync script run: | python scripts/update_subtrees.py + - name: Configure git remote authentication + env: + GH_TOKEN: ${{ github.token }} + run: | + git config --local http.https://github.com/.extraheader "AUTHORIZATION: basic $(echo -n x-access-token:${GH_TOKEN} | base64)" + - name: Commit and push updated context files - uses: stefanzweifel/git-auto-commit-action@v5 + uses: stefanzweifel/git-auto-commit-action@b863ae1933cb653a53c021fe36dbb774e1fb9403 # v5.2.0 with: commit_message: "chore(context): auto-sync client repo .agent context files" file_pattern: "repos/**" diff --git a/context.py b/context.py index 7408886..34d0c30 100644 --- a/context.py +++ b/context.py @@ -3,6 +3,9 @@ logger = logging.getLogger("pr-dashboard.context") +MAX_FILE_CHARS = 20_000 +MAX_TOTAL_CHARS = 120_000 + def get_repo_dir(repo_name: str) -> Path | None: """Find repository context directory inside repos/ or workspace.""" @@ -27,6 +30,8 @@ def load_full_repo_context(repo_name: str) -> str: context_parts = [f"=== REPOSITORY: {repo_name} ==="] loaded_files = 0 + skipped_files = 0 + total_chars = 0 # Recursively scan for all .md files inside the target repository directory for md_file in sorted(repo_dir.rglob("*.md")): @@ -34,15 +39,39 @@ def load_full_repo_context(repo_name: str) -> str: if any(part.startswith(".") and part not in [".agent"] for part in md_file.parts): continue + if total_chars >= MAX_TOTAL_CHARS: + skipped_files += 1 + continue + rel_path = md_file.relative_to(repo_dir) try: - with open(md_file, "r", encoding="utf-8") as f: - content = f.read().strip() - if content: - context_parts.append(f"--- {rel_path} ---\n{content}") - loaded_files += 1 + content = md_file.read_text(encoding="utf-8").strip() except Exception as e: logger.error(f"Error reading context file {md_file}: {e}") + continue + + if not content: + continue + + truncated = len(content) > MAX_FILE_CHARS + if truncated: + content = content[:MAX_FILE_CHARS] + + remaining = MAX_TOTAL_CHARS - total_chars + if len(content) > remaining: + content = content[:remaining] + truncated = True + + suffix = "\n... [truncated]" if truncated else "" + context_parts.append(f"--- {rel_path} ---\n{content}{suffix}") + total_chars += len(content) + loaded_files += 1 + + if skipped_files: + logger.warning( + f"Context budget ({MAX_TOTAL_CHARS} chars) reached for '{repo_name}'; " + f"skipped {skipped_files} additional markdown file(s)." + ) - logger.info(f"Loaded {loaded_files} markdown context files for '{repo_name}'") + logger.info(f"Loaded {loaded_files} markdown context files for '{repo_name}' ({total_chars} chars)") return "\n\n".join(context_parts) diff --git a/repo_metadata.py b/repo_metadata.py index 0de7751..775a12d 100644 --- a/repo_metadata.py +++ b/repo_metadata.py @@ -8,7 +8,7 @@ "GSoC-Proposal-Assistant": { "url": "https://github.com/kpj2006/GSoC-Proposal-Assistant", }, - "Template-Repo-Main": { + "Template-Repo": { "url": "https://github.com/AOSSIE-Org/Template-Repo", }, } From a4e0eabaceab7ec21e8bc47b6d0f4234c8461660 Mon Sep 17 00:00:00 2001 From: kpj2006 <24ucs074@lnmiit.ac.in> Date: Wed, 12 Aug 2026 20:59:47 +0530 Subject: [PATCH 3/3] Enhance context loading and syncing: track removed stale files and improve section handling --- context.py | 5 +++-- scripts/update_subtrees.py | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/context.py b/context.py index 34d0c30..f54b351 100644 --- a/context.py +++ b/context.py @@ -63,8 +63,9 @@ def load_full_repo_context(repo_name: str) -> str: truncated = True suffix = "\n... [truncated]" if truncated else "" - context_parts.append(f"--- {rel_path} ---\n{content}{suffix}") - total_chars += len(content) + section = f"--- {rel_path} ---\n{content}{suffix}" + context_parts.append(section) + total_chars += len(section) loaded_files += 1 if skipped_files: diff --git a/scripts/update_subtrees.py b/scripts/update_subtrees.py index eb98746..3575605 100644 --- a/scripts/update_subtrees.py +++ b/scripts/update_subtrees.py @@ -67,21 +67,33 @@ def sync_repo_context(repo_name: str, meta: dict, client: httpx.Client): headers = {"User-Agent": "PullRequestDashboard-Context-Sync"} downloaded_count = 0 + removed_count = 0 for rel_file in KNOWN_CONTEXT_FILES: raw_url = f"https://raw.githubusercontent.com/{owner}/{repo}/{ref}/{rel_file}" + dest_path = target_dir / rel_file try: res = client.get(raw_url, headers=headers) if res.status_code == 200: - dest_path = target_dir / rel_file dest_path.parent.mkdir(parents=True, exist_ok=True) dest_path.write_bytes(res.content) logger.info(f"Downloaded: repos/{repo_name}/{rel_file}") downloaded_count += 1 + elif res.status_code == 404 and dest_path.exists(): + # Confirmed absent upstream: remove the stale local copy so it + # doesn't keep getting fed into the dashboard prompt. + dest_path.unlink() + logger.info(f"Removed stale file (deleted upstream): repos/{repo_name}/{rel_file}") + removed_count += 1 + # Any other status (rate limit, server error, etc.) is not a + # confirmed absence — leave the existing local copy untouched. except Exception as e: logger.debug(f"Failed downloading {rel_file} for {repo_name}: {e}") - logger.info(f"Synced {downloaded_count} context files into repos/{repo_name}") + logger.info( + f"Synced {downloaded_count} context files into repos/{repo_name} " + f"({removed_count} stale file(s) removed)" + ) def sync_all_subtrees():