From b83cd039adb0a4965f98e2efb949018a7597e484 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hu=E1=BB=B3nh=20Th=C6=B0=C6=A1ng?=
<252359928+Huynhthuongg@users.noreply.github.com>
Date: Wed, 3 Jun 2026 07:00:15 +0700
Subject: [PATCH 1/9] Remove binary release screenshot
---
.github/workflows/ci.yml | 8 +++-
.gitignore | 3 ++
README.md | 11 ++++-
app/universal_compiler_agent/__init__.py | 2 +-
app/universal_compiler_agent/cli.py | 22 +++++++--
app/universal_compiler_agent/server.py | 54 +++++++----------------
app/universal_compiler_agent/templates.py | 5 +--
docs/CHANGELOG.md | 7 +++
docs/SPECIFICATION.md | 4 +-
pyproject.toml | 6 ++-
scripts/check.sh | 4 ++
tests/test_server.py | 36 +++++++++++++++
12 files changed, 109 insertions(+), 53 deletions(-)
create mode 100755 scripts/check.sh
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ffd6d0c..e150f07 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,12 +7,16 @@ on:
jobs:
test:
runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version: ['3.10', '3.11', '3.12']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
- python-version: '3.11'
+ python-version: ${{ matrix.python-version }}
- run: python -m pip install --upgrade pip
- run: python -m pip install -e '.[dev]'
- run: ruff check .
- - run: pytest
+ - run: pytest -q
diff --git a/.gitignore b/.gitignore
index 6b5b6d1..c2571d0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,6 @@ backup-*.tar.gz
# Local configuration
.env
+
+# Generated release screenshots (avoid binary files in Codex diffs)
+docs/screenshots/*-release.png
diff --git a/README.md b/README.md
index ff843c9..dd1dadf 100644
--- a/README.md
+++ b/README.md
@@ -62,10 +62,17 @@ tests/ Unit tests
```bash
python -m pip install -e '.[dev]'
-ruff check .
-pytest
+./scripts/check.sh
```
+`./scripts/check.sh` runs Ruff and the full pytest suite so release checks match CI.
+
+## Current release
+
+- Version: 0.1.1
+- Release date: 2026-06-02
+- Release focus: CLI dry-run previews, safe output directory validation, dashboard route alignment, and test workflow hardening.
+
## Security model
- Never hardcode secrets in generated output.
diff --git a/app/universal_compiler_agent/__init__.py b/app/universal_compiler_agent/__init__.py
index 04fee82..adaab43 100644
--- a/app/universal_compiler_agent/__init__.py
+++ b/app/universal_compiler_agent/__init__.py
@@ -1,4 +1,4 @@
"""Universal Project Compiler Agent package."""
__all__ = ["__version__"]
-__version__ = "0.1.0"
+__version__ = "0.1.1"
diff --git a/app/universal_compiler_agent/cli.py b/app/universal_compiler_agent/cli.py
index 41d8ca0..d2457ad 100644
--- a/app/universal_compiler_agent/cli.py
+++ b/app/universal_compiler_agent/cli.py
@@ -19,6 +19,14 @@ def _read_input(args: argparse.Namespace) -> str:
return "Universal Project Compiler Agent"
+def _safe_output_dir(value: str) -> Path:
+ output_dir = Path(value)
+ if output_dir.is_absolute() or ".." in output_dir.parts:
+ msg = "output_dir must be a safe relative path"
+ raise argparse.ArgumentTypeError(msg)
+ return output_dir
+
+
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Compile requirements into a runnable project scaffold."
@@ -37,7 +45,15 @@ def build_parser() -> argparse.ArgumentParser:
compile_cmd.add_argument("--text", help="Inline requirements text.")
compile_cmd.add_argument("--name", help="Override generated project name.")
compile_cmd.add_argument(
- "--output-dir", default="generated", help="Directory that will receive output."
+ "--dry-run",
+ action="store_true",
+ help="Print the generated plan as JSON without writing files.",
+ )
+ compile_cmd.add_argument(
+ "--output-dir",
+ default=Path("generated"),
+ type=_safe_output_dir,
+ help="Safe relative directory that will receive output.",
)
return parser
@@ -47,12 +63,12 @@ def main(argv: list[str] | None = None) -> int:
args = parser.parse_args(argv)
requirements = _read_input(args)
- if args.command == "plan":
+ if args.command == "plan" or args.dry_run:
plan = build_plan(requirements, args.name)
print(json.dumps(asdict(plan), indent=2))
return 0
- result = compile_project(requirements, Path(args.output_dir), args.name)
+ result = compile_project(requirements, args.output_dir, args.name)
print(f"Generated {result.file_count} files in {result.root}")
return 0
diff --git a/app/universal_compiler_agent/server.py b/app/universal_compiler_agent/server.py
index aff944b..1bf05b7 100644
--- a/app/universal_compiler_agent/server.py
+++ b/app/universal_compiler_agent/server.py
@@ -11,6 +11,9 @@
from .compiler import compile_project
from .planner import build_plan
+from .templates import INDEX_HTML
+
+APP_VERSION = "0.1.1"
class PlanRequest(BaseModel):
@@ -22,8 +25,15 @@ class CompileRequest(PlanRequest):
output_dir: str = Field(default="generated", max_length=240)
+def _safe_output_dir(value: str) -> Path:
+ output_dir = Path(value)
+ if output_dir.is_absolute() or ".." in output_dir.parts:
+ raise HTTPException(status_code=400, detail="output_dir must be a safe relative path")
+ return output_dir
+
+
def create_app() -> FastAPI:
- app = FastAPI(title="Universal Project Compiler Agent", version="0.1.0")
+ app = FastAPI(title="Universal Project Compiler Agent", version=APP_VERSION)
@app.middleware("http")
async def security_headers(request: Request, call_next): # type: ignore[no-untyped-def]
@@ -36,38 +46,7 @@ async def security_headers(request: Request, call_next): # type: ignore[no-unty
@app.get("/", response_class=HTMLResponse)
def index() -> str:
- return """
-
-
-
-
-
- Universal Project Compiler Agent
-
-
-
-
- Android-first • Termux-first • Production-ready
- Compile requirements into runnable software projects.
- Use POST /plan to analyze requirements or POST /compile to emit a secure, maintainable scaffold with docs, tests, and scripts.
-
- CLI
upca compile --input-file spec.md
- API
JSON endpoints for automation and future UI integrations.
- Security
Path safety, secret redaction, and hardened HTTP headers.
-
-"""
+ return INDEX_HTML
@app.get("/health")
def health() -> dict[str, str]:
@@ -80,10 +59,11 @@ def plan(request: PlanRequest) -> JSONResponse:
@app.post("/compile")
def compile_endpoint(request: CompileRequest) -> dict[str, object]:
- output_dir = Path(request.output_dir)
- if output_dir.is_absolute() or ".." in output_dir.parts:
- raise HTTPException(status_code=400, detail="output_dir must be a safe relative path")
- result = compile_project(request.requirements, output_dir, request.project_name)
+ result = compile_project(
+ request.requirements,
+ _safe_output_dir(request.output_dir),
+ request.project_name,
+ )
return {"root": str(result.root), "file_count": result.file_count, "slug": result.plan.slug}
return app
diff --git a/app/universal_compiler_agent/templates.py b/app/universal_compiler_agent/templates.py
index 380cb8e..806a73e 100644
--- a/app/universal_compiler_agent/templates.py
+++ b/app/universal_compiler_agent/templates.py
@@ -10,7 +10,6 @@
Universal Project Compiler Agent
-
+
+
+
+
+
+
+
+
+
+
Search Weather
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+