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/3] 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 -