Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@ on:
pull_request:
branches: [main]

permissions:
contents: read

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
planning-governance:
name: Planning Governance
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down Expand Up @@ -62,6 +70,7 @@ jobs:
backend-tests:
name: Backend Tests
runs-on: ubuntu-latest
timeout-minutes: 20
env:
UDOS_ROOT: ${{ github.workspace }}/..
UDOS_HOME: ${{ github.workspace }}/.ci-udos
Expand All @@ -79,21 +88,21 @@ jobs:
uses: actions/checkout@v4
with:
repository: fredporter/uFlow
ref: work/2026-08-18-stabilise
ref: main
path: external/uFlow

- name: Checkout uKnowledge
uses: actions/checkout@v4
with:
repository: fredporter/uKnowledge
ref: work/2026-08-18-stabilise
ref: main
path: external/uKnowledge

- name: Checkout uCode runtime
uses: actions/checkout@v4
with:
repository: fredporter/uCode
ref: work/2026-08-18-stabilise
ref: main
path: external/uCode

- name: Install backend dependencies
Expand All @@ -114,6 +123,7 @@ jobs:
frontend-build:
name: Frontend Build
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -127,7 +137,7 @@ jobs:
uses: actions/checkout@v4
with:
repository: fredporter/uCode
ref: work/2026-08-18-stabilise
ref: main
path: external/uCode

- name: Expose sibling uCode source contract
Expand Down
41 changes: 0 additions & 41 deletions .github/workflows/snackmachine-smoke.yml

This file was deleted.

25 changes: 0 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,31 +55,6 @@ curl http://localhost:8484/api/health
./scripts/install.sh --uninstall
```

### Manual Smoke Workflow (GitHub Actions)

uCore includes a manual workflow for SnackMachine integration smoke:

- Workflow name: SnackMachine Smoke
- Workflow file: .github/workflows/snackmachine-smoke.yml
- Trigger: Actions -> SnackMachine Smoke -> Run workflow

Inputs:

- ucore_url (default: http://127.0.0.1:8484)
- frontend_url (default: http://localhost:5175)
- snackmachine_path (default: $HOME/Code/SnackMachine)

Self-hosted runner prerequisites:

- uCore backend running and reachable at ucore_url
- uCore frontend running and reachable at frontend_url
- SnackMachine repo checked out at snackmachine_path
- Python 3 available on runner PATH

What it runs:

- bash scripts/smoke_snackmachine_integration.sh

## Architecture

uCore is the **host platform core**. Optional capabilities — workflow,
Expand Down
89 changes: 89 additions & 0 deletions backend/app/api/developer_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import json
import subprocess
from pathlib import Path
from typing import Any
Expand Down Expand Up @@ -184,6 +185,80 @@ def _git_output(repo_path: Path, *args: str) -> str:
return result.stdout.strip()


def _github_json(repo_path: Path, *args: str) -> Any:
"""Run one read-only gh query against a repository remote."""
try:
result = subprocess.run(
["gh", *args],
cwd=repo_path,
capture_output=True,
text=True,
timeout=10,
check=False,
)
except (OSError, subprocess.TimeoutExpired):
return None
if result.returncode != 0:
return None
try:
return json.loads(result.stdout)
except json.JSONDecodeError:
return None


def _repo_github_status(repo_name: str) -> dict[str, Any]:
"""Return the GitHub/Actions state used by the Developer surface."""
repo_path = _repo_path(repo_name)
branch = _git_output(repo_path, "rev-parse", "--abbrev-ref", "HEAD") or "unknown"
repository = _github_json(
repo_path,
"repo",
"view",
"--json",
"nameWithOwner,url,defaultBranchRef",
)
if not isinstance(repository, dict):
return {
"repo": repo_name,
"configured": False,
"branch": branch,
"error": "GitHub CLI is unavailable, unauthenticated, or the remote is not on GitHub",
}

runs = _github_json(
repo_path,
"run",
"list",
"--branch",
branch,
"--limit",
"5",
"--json",
"databaseId,workflowName,status,conclusion,headBranch,event,createdAt,url",
)
prs = _github_json(
repo_path,
"pr",
"list",
"--head",
branch,
"--state",
"open",
"--limit",
"1",
"--json",
"number,title,state,isDraft,url,statusCheckRollup",
)
return {
"repo": repo_name,
"configured": True,
"branch": branch,
"repository": repository,
"pull_request": prs[0] if isinstance(prs, list) and prs else None,
"runs": runs if isinstance(runs, list) else [],
}


def _repo_file_count(repo_path: Path, limit: int = 500) -> int:
count = 0
for path in repo_path.rglob("*"):
Expand Down Expand Up @@ -700,6 +775,19 @@ async def handle_list_repos(request: web.Request) -> web.Response:
return web.json_response({"repos": repos, "count": len(repos)})


async def handle_repo_github_status(request: web.Request) -> web.Response:
"""GET /api/developer/repos/{repo_name}/github — PR and Actions state."""
repo_name = request.match_info["repo_name"]
try:
payload = _repo_github_status(repo_name)
except FileNotFoundError:
return web.json_response(
{"error": f"Repository not found: {repo_name}"},
status=404,
)
return web.json_response(payload)


async def handle_list_repo_files(request: web.Request) -> web.Response:
repo_name = request.match_info["repo_name"]
include_hidden = _to_bool(request.query.get("include_hidden"), default=False)
Expand Down Expand Up @@ -939,6 +1027,7 @@ async def handle_developer_chat(request: web.Request) -> web.Response:
"• /api/developer/repos/{name}/files — list files in a repo\n"
"• /api/developer/repos/{name}/review — git status review of changes\n"
"• /api/developer/repos/{name}/status — staged/unstaged file status\n"
"• /api/developer/repos/{name}/github — GitHub PR and Actions status\n"
"• /api/developer/repos/{name}/diff?path=... — view file diff\n"
"• /api/developer/repos/{name}/file-preview?path=... — preview file content\n"
"• /api/developer/repos/{name}/stage — stage a file (POST)\n"
Expand Down
Loading
Loading