diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..77fa1df --- /dev/null +++ b/.gitattributes @@ -0,0 +1,15 @@ +# Normalise line endings so shell scripts and the C++ shim run unchanged on +# Windows (Git Bash / WSL) checkouts. Binaries are left untouched. +* text=auto + +scripts/cf text eol=lf +*.sh text eol=lf +Makefile text eol=lf +*.cpp text eol=lf +*.h text eol=lf +*.c++ text eol=lf + +*.png binary +*.ico binary +*.jpg binary +*.gif binary diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0cea9dd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,123 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + cli: + name: CLI (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + steps: + - uses: actions/checkout@v4 + + - name: Install shellcheck (macOS) + if: runner.os == 'macOS' + run: brew install shellcheck coreutils + + - name: shellcheck + run: shellcheck -s bash scripts/cf scripts/check.sh scripts/test.sh scripts/validate.sh tests/cli_test.sh + + - name: Toolchain + run: | + which g++ clang++ || true + (g++ --version || clang++ --version) | head -1 + + - name: CLI test suite + run: bash tests/cli_test.sh + + - name: Makefile smoke test + run: | + make all + make test FILE=watermelon + + web: + name: Web (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + defaults: + run: + working-directory: web + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: web/package-lock.json + + - name: Install + run: npm ci --include=dev --no-audit --no-fund + + - name: Lint + run: npm run lint + + - name: Typecheck + run: npm run typecheck + + - name: Unit tests + run: npm test + + - name: Build + run: npm run build + + - name: Version consistency + working-directory: . + run: | + cli=$(sed -n 's/^CF_VERSION="\(.*\)"$/\1/p' scripts/cf) + pkg=$(node -p "require('./web/package.json').version") + test "$cli" = "$pkg" || { echo "scripts/cf=$cli web/package.json=$pkg"; exit 1; } + + e2e: + name: End-to-end (Playwright) + runs-on: ubuntu-latest + needs: [web] + defaults: + run: + working-directory: web + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: web/package-lock.json + + - name: Install + run: npm ci --include=dev --no-audit --no-fund + + - name: Install Playwright browser + run: npx playwright install --with-deps chromium + + - name: Playwright + run: npx playwright test + env: + CI: "1" + + - name: Upload report on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: playwright-report + path: | + web/playwright-report + web/test-results + retention-days: 7 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9229ff9 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,43 @@ +name: Release + +on: + push: + tags: ["v*"] + +permissions: + contents: write + +jobs: + release: + name: Publish GitHub release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check the tag matches the shipped version + run: | + tag="${GITHUB_REF_NAME#v}" + cli=$(sed -n 's/^CF_VERSION="\(.*\)"$/\1/p' scripts/cf) + pkg=$(node -p "require('./web/package.json').version") + test "$tag" = "$cli" && test "$tag" = "$pkg" || { + echo "tag=$tag scripts/cf=$cli web/package.json=$pkg"; exit 1; } + + - name: Extract release notes from CHANGELOG.md + id: notes + run: | + version="${GITHUB_REF_NAME#v}" + awk -v v="$version" ' + /^## / { if (found) exit; if (index($0, "[" v "]") || index($0, " " v " ") || $2 == v) { found = 1; next } } + found { print } + ' CHANGELOG.md > release-notes.md + if [ ! -s release-notes.md ]; then + echo "No CHANGELOG section for $version; using git log" >&2 + git log --format='- %s' "$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || git rev-list --max-parents=0 HEAD)..HEAD" > release-notes.md + fi + cat release-notes.md + + - name: Create release + uses: softprops/action-gh-release@v2 + with: + body_path: release-notes.md + generate_release_notes: false diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..37e6d61 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,122 @@ +# Changelog + +All notable changes to this project are documented here. The format follows +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses +[Semantic Versioning](https://semver.org/). + +## [1.0.0] - 2026-09-15 + +First tagged release. Everything below is relative to the untagged workbench +that preceded it. + +### Added + +**Execution engine** + +- Compile cache keyed by compiler, standard, flags and source. Re-running + unchanged code, or running one program against many test cases, no longer + rebuilds it. Identical concurrent compiles are coalesced; entries are + evicted least-recently-used and the cache is reclaimed on exit. +- Structured compiler diagnostics (`file:line:col: severity: message`) parsed + from gcc/clang output and returned by every compile. +- Output checkers: `lines` (default), `tokens` (whitespace-insensitive, the + Codeforces `wcmp` behaviour) and `float` (absolute/relative epsilon). A `WA` + whose tokens match is flagged `presentationOnly`. +- Statement parser that turns a pasted Codeforces (or AtCoder-style) problem + into sample tests plus the declared time and memory limits. +- Windows support for the engine (`prog.exe`, path normalisation in + diagnostics). + +**API** + +- `POST /api/samples` parses a statement into tests and limits. +- `GET /api/problems?export=1` and `POST /api/problems {op:"import"}` move a + whole library as one JSON bundle; `{op:"duplicate"}` copies a problem. +- `/api/test` accepts `checker`, `epsilon` and `stopOnFirstFailure`, and + reports `SKIPPED` cases, `maxTimeMs` and `totalTimeMs`. +- `/api/stress` accepts `compilerFlags`, `checker`, `epsilon` and `seedBase`, + and reports `elapsedMs`, `deadlineHit` and per-program timing statistics. +- `GET /api/config` reports the workbench version, compiler version, limits + and cache size; `DELETE /api/config?cache=1` drops the compile cache. +- `GET /api/template` lists the bundled starter templates. +- Every compile response carries `cached`, `diagnostics` and `rejectedFlags`. + +**Web workbench** + +- Gutter error/warning markers, a clickable diagnostics list that jumps to the + line, an error/warning count in the toolbar and a cursor/line/char status + bar. `Ctrl+/` toggles line comments. +- Tests panel: import samples from a pasted statement, run a single case, + duplicate a case, send a case's input to stdin, expand/collapse all, stop on + first failure, whitespace-only hints, per-run max time. +- Stress panel: seed base, timing statistics, request-budget notice, and + buttons that promote the failing input to a test case or to stdin. +- Settings: checker mode and epsilon, reset to defaults, clear the server + compile cache, and a read-only server section (compiler, limits, cache). +- Problems sidebar: whole-workspace save (source, statement, tests, stdin, + stress sources, settings), duplicate, filter, relative timestamps, unsaved + changes indicator, new-workspace button, export/import of the library. +- Resizable editor/panel split (double-click resets), the active tab and the + custom stdin persist across reloads, `Ctrl/Cmd+1..4` switch panels, + `Ctrl/Cmd+B` toggles the sidebar, and the header shows the detected + compiler. +- Toasts for run/test outcomes and an offline-aware API client. + +**CLI (`scripts/cf`)** + +- New commands: `stress`, `samples`, `watch`, `doctor`, `clean`, `version`, + plus `new` as an alias for `template`. +- `template --from dp|graph|math|` scaffolds from a bundled starter. +- `--checker lines|tokens` (or `CF_CHECKER`) on `run`, `test` and `stress`; + a lines-mode mismatch whose tokens match prints a hint. +- `--timeout` on `run`, `test` and `stress`; per-sample and maximum timing in + `cf test`; `CF_CXX`/`CXX` compiler override; `NO_COLOR`; `CF_NO_EDITOR`; + `CF_SERVE_PROD`. +- `brute.cpp`, `gen.cpp` and `generator.cpp` are excluded from the solution + build so a stress setup lives next to the solution. + +**Tooling** + +- Vitest unit suite for the engine (compile, cache, TLE/RE classification, + output caps, checkers, diagnostics, flag validation, statement parsing and + the store), run with `npm test`. +- `tests/cli_test.sh`: 57 end-to-end checks of the CLI against the real + compiler (replaces `tests/parser_test.sh` and `test_full.sh`). +- Five new Playwright specs covering diagnostics, statement import, checkers, + run-one, stress promotion and the problems library. +- `scripts/check.sh` / `make check`: shellcheck, CLI tests, lint, typecheck, + unit tests, production build and a version-consistency check. +- GitHub Actions: CI on Ubuntu and macOS (CLI, web, Playwright) and a release + workflow that publishes the CHANGELOG section for a pushed `v*` tag. +- `.gitattributes` pins LF line endings for scripts and sources so Windows + checkouts run unchanged. + +### Changed + +- Compiler flags from the browser are validated against an allowlist + (`-O`, `-W`, `-D`, `-U`, `-f`, `-g`, `-m`, `-std=`, ...). Flags that write + files or load code (`-o`, `-include`, `@file`, `-fplugin`, `-Wl,` ...) are + rejected and reported. +- `/api/solution` and `/api/problem-text` reject problem names that are not a + single safe path segment, closing a path-traversal hole. Malformed JSON on + any route now yields a 400 instead of a 500. +- The problem store writes atomically (temp file + rename) and tolerates + records written by older versions. +- The stress request budget is 60 seconds (was 30). +- `cf test ` honours the explicit argument even when the working + directory contains a `problem.txt`. +- `cf update` pulls with `--ff-only` and can skip setup with + `CF_UPDATE_SKIP_SETUP=1`. +- `scripts/test.sh` reports the program's real exit status (previously the + negated `if !` status), lints every script and delegates to the CLI suite. +- Version bumped to 1.0.0 in `scripts/cf` and `web/package.json`; CI fails if + they diverge. + +### Removed + +- `web/lib/cf.ts` (an unused wrapper that spawned the Bash CLI from Node). +- Unused Next.js scaffold assets under `web/public/`. +- `test_full.sh` and `tests/parser_test.sh` (superseded by + `tests/cli_test.sh` and `scripts/check.sh`). + +[1.0.0]: https://github.com/mbn-code/cf/releases/tag/v1.0.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index de2830f..4796d97 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,87 +1,131 @@ -# Contributing to cf Toolkit +# Contributing to cf -First off, thanks for taking the time to contribute! Contributions are what make the open-source community such an amazing place to learn, inspire, and create. +Thanks for taking the time to contribute. This page covers how to report +problems, how the repository is laid out, and what a pull request needs to +pass. -## How Can I Contribute? +## How can I contribute? -### Reporting Bugs -- Use the [Bug Report Template](.github/ISSUE_TEMPLATE/bug_report.yml). -- Provide a clear and concise description of the bug. -- Include reproduction steps and your environment details. +### Reporting bugs -### Suggesting Enhancements -- Use the [Feature Request Template](.github/ISSUE_TEMPLATE/feature_request.yml). -- Explain why the feature would be useful. +- Use the [bug report template](.github/ISSUE_TEMPLATE/bug_report.yml). +- Include the output of `cf doctor` (or the Server block of the Settings tab) + so we know the compiler and platform. +- Provide reproduction steps: the source, the input, and what you expected. -### Pull Requests -1. Fork the repo and create your branch from `main`. -2. If you've added code that should be tested, add tests. -3. Ensure the test suite passes. -4. Make sure your code follows the existing style. -5. Write a clear title and description for your pull request. +### Suggesting enhancements -> [!IMPORTANT] -> Always run `npm run lint` and `npm run build` in the `web` directory before submitting a PR to catch any TypeScript or styling errors. +- Use the [feature request template](.github/ISSUE_TEMPLATE/feature_request.yml). +- Explain the workflow the feature would improve. -## Development Setup +### Pull requests -### CLI Development -The core logic resides in `scripts/`. If you modify `cf`, `test.sh`, or `build.sh`, make sure to test them across different problem structures. +1. Fork the repository and create your branch from `main`. +2. Add or update tests for the behaviour you change (see below). +3. Run `make check` and make sure it is green. +4. Match the surrounding style; the formatter runs on save for the web app. +5. Write a clear title and description, and add a `CHANGELOG.md` entry under + an `Unreleased` heading for user-visible changes. -> [!WARNING] -> Be careful when modifying the `cf` script's path resolution logic, as it needs to work for both local clones and global installations. +## Development setup + +### CLI + +The CLI is the single Bash script `scripts/cf`. It must keep working for +both a local clone and a global installation (see the path resolution at the +top of the script), on Linux, macOS (bash 3.2 and zsh) and Git Bash on +Windows. ```bash -# Test the CLI locally -./scripts/cf --version +bash scripts/cf doctor # toolchain check +bash tests/cli_test.sh # 50+ end-to-end checks against the real compiler +shellcheck -s bash scripts/cf # must be clean ``` -### Web Interface Development -The web interface is built with **Next.js 15**, **Tailwind CSS 4**, and **Shadcn UI**. +### Web workbench + +The workbench is built with Next.js 16, React 19, Tailwind CSS 4 and +shadcn/ui. The execution engine under `web/app/api/_engine/` uses only Node +built-ins so it can be unit-tested without the framework. ```bash cd web npm install -npm run dev +npm run dev # http://localhost:3000 +npm test # vitest unit suite +npm run e2e # Playwright (npx playwright install chromium once) +npm run check # lint + typecheck + test + build ``` -The web interface communicates with the local filesystem via API routes in `web/app/api/`. +Every `/api/*` route documents its request and response shape in a comment +at the top of the file and in [docs/api.md](docs/api.md); keep the comment, +the doc and the typed client in `web/lib/api.ts` in sync when you change a +contract. + +## Project structure + +- `scripts/`: the `cf` CLI, `check.sh` (local quality gate), `validate.sh` + (release gate), `test.sh`, `build.sh`, `setup.sh`. +- `web/`: the Next.js workbench (UI, API routes, engine, unit and e2e tests). +- `src/`: sample solutions and the CLI template (`template.cpp`). +- `templates/`: C++ starters used by `cf template --from`. +- `include/`: the portable `` shim. +- `tests/`: the CLI suite and its fixtures. +- `docs/`: user and developer documentation. -## Project Structure +## Tests -- `scripts/`: Core bash tools. -- `web/`: Next.js web workbench. -- `src/`: User solutions area. -- `templates/`: C++ algorithm templates. -- `include/`: Shared C++ headers. -- `tests/`: Automated test suite for the toolkit itself. +| Layer | Command | What it proves | +| ------- | ------------------------ | ---------------------------------------------------------------- | +| Engine | `cd web && npm test` | Compile cache, verdict classification, checkers, parsers, store. | +| CLI | `bash tests/cli_test.sh` | Real `cf` runs against the real compiler. | +| Browser | `cd web && npm run e2e` | The whole workbench end to end in Chromium. | +| Gate | `make check` | shellcheck, CLI, lint, typecheck, unit tests, build, versions. | -## Style Guidelines +Tests must be able to fail for a real reason. Prefer exercising the real +compiler over mocking it. + +## Style guidelines ### Bash -- Use `[[ ]]` instead of `[ ]` for conditions. -- Quote variables to prevent word splitting. -- Use meaningful names for functions. + +- `set -euo pipefail`, quote every expansion, prefer `[ ]` with explicit + tests as the existing script does, and keep shellcheck clean. +- New commands get a `cmd_` function, a `case` entry in `main`, a help + entry, and CLI tests. ### C++ + - Follow the patterns in `src/template.cpp`. -- Use modern C++ features (C++20/23). -- Keep performance in mind (Fast I/O, efficient algorithms). - -### TypeScript/React -- Use functional components and hooks. -- Follow Tailwind CSS best practices. -- Ensure components are accessible. - -## Commit Messages -We follow a simple convention for commit messages: -- `feat:` for new features. -- `fix:` for bug fixes. -- `docs:` for documentation changes. -- `refactor:` for code changes that neither fix a bug nor add a feature. -- `chore:` for updating build tasks, etc. - -Example: `feat: add support for interactive problems in web UI` - -## Code of Conduct -Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. +- Anything shipped as a template must compile with `-std=c++23 -Wall -Wextra` + and against the shim. + +### TypeScript / React + +- Functional components and hooks; keep the API tree free of imports from + `web/lib`. +- Expose a stable `data-testid` on anything the e2e suite needs. +- Guard every `localStorage` access so server rendering keeps working. + +## Commit messages + +Use the conventional prefixes already in the history: + +- `feat:` new features +- `fix:` bug fixes +- `docs:` documentation +- `refactor:` code changes that neither fix a bug nor add a feature +- `chore:` build, CI and tooling + +Example: `feat: import samples from a pasted statement` + +## Releasing + +See [docs/development.md](docs/development.md#releasing). Versions live in +`scripts/cf` (`CF_VERSION`) and `web/package.json` and must match; CI +enforces it. + +## Code of conduct + +This project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). +By participating you agree to abide by its terms. diff --git a/Makefile b/Makefile index 2a9b283..865a26f 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,14 @@ # make run FILE=x - Compile and run, with a portable timeout + timing # make test FILE=x - Compile and run against src/x/input.txt, diff expected # make debug FILE=x - Compile with debug symbols (-g) +# make check - Shellcheck + CLI tests + web lint/typecheck/unit/build +# make check-quick - Same without the production build +# make web-install - npm install in web/ +# make web-dev - Start the workbench dev server +# make web-build - Production build of the workbench +# make web-test - Unit tests (vitest) +# make web-e2e - Playwright end-to-end suite +# make validate - Full release gate (scripts/validate.sh) # make clean - Remove build artifacts # make help - Show this help # @@ -27,7 +35,7 @@ # works on macOS (bash 3.2 / zsh) without coreutils. ################################################################################ -.PHONY: all build run test debug clean help +.PHONY: all build run test debug clean help check check-quick web-install web-dev web-build web-test web-e2e validate # ==================== Configuration ==================== @@ -39,12 +47,22 @@ CXXFLAGS ?= -std=$(CXXSTD) -O2 -Wall -Wextra INCLUDE := -I include SRC_DIR := src BUILD_DIR := build +WEB_DIR := web FILE ?= solution TL ?= 5 .DEFAULT_GOAL := help -# ==================== Targets ==================== +# Resolve FILE to a source path (shared by build/run/test/debug). +define resolve_src + src=""; \ + if [ -f "$(SRC_DIR)/$(FILE).cpp" ]; then src="$(SRC_DIR)/$(FILE).cpp"; \ + elif [ -f "$(SRC_DIR)/$(FILE)/solution.cpp" ]; then src="$(SRC_DIR)/$(FILE)/solution.cpp"; \ + elif [ -f "$(FILE)" ]; then src="$(FILE)"; fi; \ + if [ -z "$$src" ]; then echo "error: cannot find source for FILE=$(FILE)"; exit 1; fi +endef + +# ==================== C++ targets ==================== all: @mkdir -p $(BUILD_DIR); \ @@ -62,22 +80,14 @@ all: build: @mkdir -p $(BUILD_DIR); \ - src=""; \ - if [ -f "$(SRC_DIR)/$(FILE).cpp" ]; then src="$(SRC_DIR)/$(FILE).cpp"; \ - elif [ -f "$(SRC_DIR)/$(FILE)/solution.cpp" ]; then src="$(SRC_DIR)/$(FILE)/solution.cpp"; \ - elif [ -f "$(FILE)" ]; then src="$(FILE)"; fi; \ - if [ -z "$$src" ]; then echo "error: cannot find source for FILE=$(FILE)"; exit 1; fi; \ + $(resolve_src); \ out="$(BUILD_DIR)/$(FILE)"; mkdir -p "`dirname "$$out"`"; \ echo "Compiling $$src -> $$out with $(CXX) ..."; \ $(CXX) $(CXXFLAGS) $(INCLUDE) "$$src" -o "$$out" && echo "OK: $$out" run: @mkdir -p $(BUILD_DIR); \ - src=""; \ - if [ -f "$(SRC_DIR)/$(FILE).cpp" ]; then src="$(SRC_DIR)/$(FILE).cpp"; \ - elif [ -f "$(SRC_DIR)/$(FILE)/solution.cpp" ]; then src="$(SRC_DIR)/$(FILE)/solution.cpp"; \ - elif [ -f "$(FILE)" ]; then src="$(FILE)"; fi; \ - if [ -z "$$src" ]; then echo "error: cannot find source for FILE=$(FILE)"; exit 1; fi; \ + $(resolve_src); \ bin="$(BUILD_DIR)/cf_run_bin"; \ echo "Compiling $$src with $(CXX) ..."; \ if ! $(CXX) $(CXXFLAGS) $(INCLUDE) "$$src" -o "$$bin"; then echo "compile failed"; exit 1; fi; \ @@ -100,11 +110,7 @@ run: test: @mkdir -p $(BUILD_DIR); \ - src=""; \ - if [ -f "$(SRC_DIR)/$(FILE).cpp" ]; then src="$(SRC_DIR)/$(FILE).cpp"; \ - elif [ -f "$(SRC_DIR)/$(FILE)/solution.cpp" ]; then src="$(SRC_DIR)/$(FILE)/solution.cpp"; \ - elif [ -f "$(FILE)" ]; then src="$(FILE)"; fi; \ - if [ -z "$$src" ]; then echo "error: cannot find source for FILE=$(FILE)"; exit 1; fi; \ + $(resolve_src); \ bin="$(BUILD_DIR)/cf_test_bin"; \ echo "Compiling $$src with $(CXX) ..."; \ if ! $(CXX) $(CXXFLAGS) $(INCLUDE) "$$src" -o "$$bin"; then echo "compile failed"; exit 1; fi; \ @@ -129,11 +135,7 @@ test: debug: @mkdir -p $(BUILD_DIR); \ - src=""; \ - if [ -f "$(SRC_DIR)/$(FILE).cpp" ]; then src="$(SRC_DIR)/$(FILE).cpp"; \ - elif [ -f "$(SRC_DIR)/$(FILE)/solution.cpp" ]; then src="$(SRC_DIR)/$(FILE)/solution.cpp"; \ - elif [ -f "$(FILE)" ]; then src="$(FILE)"; fi; \ - if [ -z "$$src" ]; then echo "error: cannot find source for FILE=$(FILE)"; exit 1; fi; \ + $(resolve_src); \ out="$(BUILD_DIR)/$(FILE)_debug"; mkdir -p "`dirname "$$out"`"; \ echo "Compiling $$src with debug symbols ..."; \ $(CXX) $(CXXFLAGS) -g $(INCLUDE) "$$src" -o "$$out" && echo "Debug binary: $$out" @@ -143,10 +145,38 @@ clean: find $(SRC_DIR) -name '*.o' -delete 2>/dev/null || true; \ echo "Cleaned build artifacts" +# ==================== Quality gates ==================== + +check: + @bash scripts/check.sh + +check-quick: + @bash scripts/check.sh --quick + +validate: + @bash scripts/validate.sh + +# ==================== Web workbench ==================== + +web-install: + @cd $(WEB_DIR) && npm install --include=dev --no-audit --no-fund + +web-dev: + @cd $(WEB_DIR) && npm run dev + +web-build: + @cd $(WEB_DIR) && npm run build + +web-test: + @cd $(WEB_DIR) && npm test + +web-e2e: + @cd $(WEB_DIR) && npx playwright install chromium && npm run e2e + help: @echo "cf Makefile - portable C++ build/run/test"; \ echo ""; \ - echo "Targets:"; \ + echo "C++ targets:"; \ echo " make all Compile every src/**/*.cpp into build/"; \ echo " make build FILE=x Compile one source"; \ echo " make run FILE=x Compile and run (timeout $(TL)s, timed)"; \ @@ -154,6 +184,14 @@ help: echo " make debug FILE=x Compile with -g"; \ echo " make clean Remove build artifacts"; \ echo ""; \ + echo "Quality gates:"; \ + echo " make check shellcheck + CLI tests + web lint/typecheck/unit/build"; \ + echo " make check-quick Same, without the production build"; \ + echo " make validate Full release gate incl. Playwright e2e"; \ + echo ""; \ + echo "Web workbench:"; \ + echo " make web-install | web-dev | web-build | web-test | web-e2e"; \ + echo ""; \ echo "Knobs: FILE, TL (time limit s), CXXSTD, CXXFLAGS, CXX"; \ echo "Compiler: $(CXX)"; \ echo "Flags: $(CXXFLAGS) $(INCLUDE)"; \ diff --git a/README.md b/README.md index 6f3fa90..c1519b8 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,15 @@ # cf Workbench -**A local Codeforces-style workbench for writing, running, and stress-testing C++ solutions.** +**A local Codeforces-style workbench for writing, running, testing and stress-testing C++ solutions.** +[![CI](https://github.com/mbn-code/cf/actions/workflows/ci.yml/badge.svg)](https://github.com/mbn-code/cf/actions/workflows/ci.yml) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![C++](https://img.shields.io/badge/C%2B%2B-gnu%2B%2B17%20default-blue.svg)](https://en.cppreference.com/w/cpp/17) [![Next.js](https://img.shields.io/badge/Next.js-16-black.svg)](https://nextjs.org/) -[![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20WSL-success.svg)](README.md) +[![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20WSL%20%7C%20Windows-success.svg)](README.md) -[Features](#features) - [Quick start](#quick-start) - [How it works](#how-it-works) - [Documentation](#documentation) +[Features](#features) - [Quick start](#quick-start) - [CLI](#command-line-tool) - [How it works](#how-it-works) - [Documentation](#documentation) @@ -18,43 +19,73 @@ ## About `cf` is a self-hosted workbench for competitive programming in C++. It pairs a -browser IDE (a Next.js app under `web/`) with a server-side execution engine that -compiles and runs your code against the **real** C++ toolchain on your machine. -Everything runs locally: there is no sandbox VM, no remote judge, and no account. - -The workbench is built to make the macOS toolchain a first-class target. The -`#include ` idiom is a GCC/libstdc++ detail that Apple clang does -not ship, so the repository bundles a portable shim at -[`include/bits/stdc++.h`](include/bits/stdc++.h) and every compile is invoked with -`-I include`. The same flag is used by the web engine and the [`Makefile`](Makefile), -so code that compiles in the UI compiles from the terminal too. - -This is a stable release: the engine, the UI, and the build tooling are feature -complete and the production build (`npm run build`) passes clean. +browser IDE (a Next.js app under `web/`) and a Bash CLI (`scripts/cf`) with a +server-side execution engine that compiles and runs your code against the +**real** C++ toolchain on your machine. Everything runs locally: there is no +sandbox VM, no remote judge, and no account. + +The workbench treats every toolchain as a first-class target. The +`#include ` idiom is a GCC/libstdc++ detail that Apple clang +does not ship, so the repository bundles a portable shim at +[`include/bits/stdc++.h`](include/bits/stdc++.h) and every compile is invoked +with `-I include`. The same flag is used by the web engine, the CLI and the +[`Makefile`](Makefile), so code that compiles in the UI compiles from the +terminal too. Linux (g++), macOS (Apple clang), WSL and Windows (MinGW or +LLVM, via Git Bash) are all exercised by the test suites. --- ## Features -- **C++ editor** with Prism syntax highlighting, a synced line-number gutter, - soft-tab handling, and an adjustable font. Editor contents persist to - `localStorage` across reloads. -- **Insertable templates** (Minimal, A + B, Fast I/O + helpers, Brute force, - Generator) plus a Settings panel for the language standard, per-run time limit, - and extra compiler flags. -- **Run panel** that POSTs to `/api/run` with custom stdin and renders the verdict, - exit code, elapsed wall-clock time, compiler, and a scrollable raw terminal log - of stdout / stderr / compile errors. -- **Tests panel** to add, edit, delete, and paste-and-split sample cases, then - _Run all_ via `/api/test` for per-case **AC / WA / TLE / RE / CE** badges with an - expandable, whitespace-tolerant diff. -- **Stress panel** that compiles a solution, a brute force, and a generator, runs - them for a configurable number of iterations, and surfaces the first failing - input. It degrades gracefully if the endpoint is unavailable. -- **Problems sidebar** to save, load, rename, and delete problems and their test - cases, persisted as JSON under `web/data/`. -- **Keyboard shortcuts**: `Cmd/Ctrl+Enter` runs, `Cmd/Ctrl+Shift+Enter` runs all - tests, `Cmd/Ctrl+S` saves the current problem. +### Browser workbench + +- **C++ editor** with Prism syntax highlighting, a line-number gutter that + shows compiler error and warning markers, soft tabs, `Ctrl+/` comment + toggling, a cursor position status bar and an adjustable font. Contents + persist to `localStorage` across reloads. +- **Compiler diagnostics** parsed into a clickable list: click an error to + jump to the offending line. Disallowed compiler flags are reported rather + than silently dropped. +- **Compile cache**: a source that has already been built is not rebuilt. + Running the same program against twenty cases, or re-running after editing + only the stdin, skips the multi-second `` instantiation. +- **Run panel** with custom stdin, verdict, exit code, wall-clock time, + compile time and a scrollable raw terminal log. +- **Tests panel** to add, edit, duplicate and delete sample cases, run all of + them or a single one, stop on the first failure, and see per-case + **AC / WA / TLE / RE / CE** badges with a line diff. **Import from + statement** turns a pasted Codeforces problem into test cases and applies + the statement's time limit. +- **Output checkers**: exact lines (trailing whitespace ignored), + whitespace-insensitive tokens (the Codeforces `wcmp` checker), or floating + point with an absolute/relative epsilon. A `WA` whose tokens match is + flagged as a whitespace-only difference. +- **Stress panel** that compiles a solution, a brute force and a generator, + runs them for a configurable number of seeded iterations, reports timing + statistics, and surfaces the first failing input. One click adds that input + as a test case (with the brute force's answer as expected output) or sends + it to the Run panel's stdin. +- **Problems sidebar** that saves the whole workspace (source, statement, + tests, stdin, stress sources and settings) as JSON under `web/data/`, with + load, rename, duplicate, delete, filter, an unsaved-changes indicator, and + export/import of the whole library. +- **Resizable layout**, remembered tab, and keyboard shortcuts: + `Ctrl/Cmd+Enter` run, `Ctrl/Cmd+Shift+Enter` run all, `Ctrl/Cmd+S` save, + `Ctrl/Cmd+1..4` switch panels, `Ctrl/Cmd+B` toggle the sidebar. + +### Command-line tool + +- `cf test` parses `problem.txt` (pasted straight from Codeforces), runs every + sample with a timeout, prints a diff on failure and reports per-sample + timing. +- `cf stress` runs `solution.cpp` against `brute.cpp` on inputs from + `gen.cpp`, saving the first counter-example to `stress_fail.txt`. +- `cf watch` re-runs the samples whenever a source file changes. +- `cf template --from dp|graph|math` scaffolds a problem directory from + one of the bundled starters; `cf samples`, `cf doctor`, `cf clean` and + `cf version` round out the toolkit. +- Line and token checkers (`--checker tokens` or `CF_CHECKER=tokens`), a + build cache keyed by source and flags, and `NO_COLOR` support. See [`docs/features.md`](docs/features.md) for the complete feature guide. @@ -64,9 +95,12 @@ See [`docs/features.md`](docs/features.md) for the complete feature guide. ### Prerequisites -- A working C++ compiler. Apple clang (`clang++` / `c++`) or GCC (`g++`) both work. - On macOS, install the Xcode Command Line Tools: `xcode-select --install`. -- Node.js 20 or newer (the web app targets Next.js 16). +- A working C++ compiler. Apple clang (`clang++` / `c++`), GCC (`g++`) and + MinGW/LLVM on Windows all work. On macOS, install the Xcode Command Line + Tools: `xcode-select --install`. +- Node.js 20 or newer for the web workbench (the app targets Next.js 16). +- Bash for the CLI (Git Bash on Windows). `timeout` (GNU coreutils) enables + time limits in the CLI; on macOS `brew install coreutils`. ### Run the workbench @@ -77,8 +111,10 @@ npm install npm run dev ``` -Open . The editor loads with an A + B program and a `2 3 -> 5` -sample test, so you can click **Run** and see a result immediately. +Open . The editor loads with an A + B program and a +`2 3 -> 5` sample test, so you can click **Run** and see a result +immediately. Paste a Codeforces statement into **Tests -> From statement** to +turn its examples into test cases. For a production build: @@ -87,15 +123,31 @@ npm run build npm run start ``` +### Use the CLI + +```bash +# optional: put `cf` on your PATH +ln -s "$PWD/scripts/cf" ~/.local/bin/cf # or: bash scripts/setup.sh + +cf doctor # verify compiler, shim, timeout, node +cf template 1000A # ./1000A/solution.cpp + problem.txt +cd 1000A # paste the statement into problem.txt +cf test # run every sample, timed, with diffs +cf test --checker tokens # whitespace-insensitive comparison +cf stress -n 500 # needs brute.cpp and gen.cpp next to solution.cpp +cf serve # open this problem in the web workbench +``` + ### Build from the terminal -The repository also ships a portable [`Makefile`](Makefile) that uses the same -compiler detection and `-I include` flag as the engine: +The [`Makefile`](Makefile) uses the same compiler detection and `-I include` +flag as the engine: ```bash make build FILE=src/solution.cpp # compile one source make run FILE=src/solution.cpp # compile and run with a timeout make test FILE=myproblem # compile and diff vs src/myproblem/input.txt +make check # shellcheck + CLI tests + web lint/typecheck/unit/build ``` Full setup, build, and test instructions are in @@ -107,33 +159,43 @@ Full setup, build, and test instructions are in ``` cf/ +|- scripts/cf Bash CLI: template, run, test, stress, watch, serve, doctor |- web/ Next.js workbench (UI + API) | |- app/ -| | |- page.tsx Workbench shell (editor, panels, shortcuts) +| | |- page.tsx Workbench shell (editor, panels, shortcuts, layout) | | |- api/ | | | |- _engine/ Server execution engine (Node built-ins only) -| | | | |- cpp.ts Compiler detection, compile, run, time-limit, caps -| | | | |- compare.ts Whitespace-tolerant output comparison + diff -| | | | |- store.ts JSON problem store under web/data -| | | |- run/ POST /api/run - compile + run once -| | | |- test/ POST /api/test - run many cases, AC/WA/... -| | | |- stress/ POST /api/stress - solution vs brute + generator -| | | |- problems/ CRUD for saved problems -| | | |- {config,problem-text,solution,template}/ -| | |- components/ Editor, Run/Tests/Stress panels, sidebar, badges +| | | | |- cpp.ts Compiler detection, compile cache, run, time limit, caps +| | | | |- compare.ts lines / tokens / float checkers + line diff +| | | | |- diagnostics.ts gcc/clang stderr -> structured diagnostics +| | | | |- flags.ts Compiler-flag allowlist and -std validation +| | | | |- statement.ts Codeforces statement -> samples + limits +| | | | |- store.ts Atomic JSON problem store under web/data +| | | |- run/ POST /api/run - compile + run once +| | | |- test/ POST /api/test - run many cases, AC/WA/... +| | | |- stress/ POST /api/stress - solution vs brute + generator +| | | |- samples/ POST /api/samples - parse a pasted statement +| | | |- problems/ CRUD + export/import for saved problems +| | | |- config/ Toolchain facts, compile-cache reset +| | |- components/ Editor, Run/Tests/Stress/Settings panels, sidebar | | |- lib/ Typed API client, localStorage helpers, templates -| |- data/problems/ Saved problems (one JSON file per problem) +| |- e2e/ Playwright suite (real browser, real compiler) +| |- data/problems/ Saved problems (one JSON file per problem) |- include/bits/stdc++.h Portable shim for clang/libc++ -|- Makefile Portable terminal build/run/test +|- templates/ dp / graph / math starters for `cf template --from` +|- tests/cli_test.sh CLI end-to-end suite (real compiler) +|- Makefile Portable terminal build/run/test + quality gates |- docs/ This documentation ``` -The execution engine compiles each submission to a unique `os.tmpdir()` directory -with `-std=gnu++17 -O2 -I include`, measures wall-clock time inside Node with -`process.hrtime.bigint()`, enforces the time limit with `SIGKILL` (reported as TLE), -caps stdin / stdout / stderr at 4 MiB, and distinguishes compile errors (CE) from -runtime errors (RE) and spawn failures. Output comparison tolerates trailing -whitespace and trailing blank lines. +The execution engine writes each submission to a unique temp directory, +compiles it with `-std=gnu++17 -O2 -I include` plus any allowlisted extra +flags, and caches the binary by a hash of compiler, standard, flags and +source. Runs are measured with `process.hrtime.bigint()`, killed with +`SIGKILL` at the time limit (reported as TLE), and capped at 4 MiB of stdin, +stdout and stderr. Compile errors (CE) are distinguished from runtime errors +(RE) and spawn failures, and compiler output is parsed into +`file:line:col` diagnostics. A deeper walkthrough lives in [`docs/architecture.md`](docs/architecture.md). @@ -141,34 +203,40 @@ A deeper walkthrough lives in [`docs/architecture.md`](docs/architecture.md). ## Documentation -| Document | Contents | -| ---------------------------------------------------- | --------------------------------------------------------- | -| [`docs/features.md`](docs/features.md) | Full feature guide for every panel and setting. | -| [`docs/architecture.md`](docs/architecture.md) | The execution engine, the macOS shim, and the data store. | -| [`docs/api.md`](docs/api.md) | Request / response contract for every `/api/*` route. | -| [`docs/development.md`](docs/development.md) | Install, run, build, and the Playwright e2e suite. | -| [`docs/troubleshooting.md`](docs/troubleshooting.md) | Compiler-not-found, time limits, and macOS notes. | +| Document | Contents | +| ---------------------------------------------------- | --------------------------------------------------------------- | +| [`docs/features.md`](docs/features.md) | Full feature guide for every panel, setting and CLI command. | +| [`docs/architecture.md`](docs/architecture.md) | The execution engine, checkers, the macOS shim, the data store. | +| [`docs/api.md`](docs/api.md) | Request / response contract for every `/api/*` route. | +| [`docs/development.md`](docs/development.md) | Install, run, build, unit tests, the Playwright suite and CI. | +| [`docs/troubleshooting.md`](docs/troubleshooting.md) | Compiler-not-found, time limits, Windows and macOS notes. | +| [`CHANGELOG.md`](CHANGELOG.md) | Release history. | --- -## Command-line tools +## Command-line tool -Alongside the workbench, the repository ships a Bash CLI (`scripts/cf`) and the -`Makefile` for terminal-first workflows: +| Command | Description | +| ----------------------------------- | -------------------------------------------------------------------------------------------- | +| `cf template [--from T]` | Scaffold `.//` with `solution.cpp` and `problem.txt` (T: dp, graph, math). | +| `cf [input]` | Compile and run a solution against a sample, a file or inline input. | +| `cf test [name] [--checker tokens]` | Run every sample in `problem.txt` with a timeout, timing and diffs. | +| `cf stress [name] [-n N]` | Solution vs `brute.cpp` on `gen.cpp` inputs; saves the first counter-example. | +| `cf samples [name]` | Print the samples parsed from `problem.txt`. | +| `cf watch [name]` | Re-run the samples whenever a source file changes. | +| `cf serve [name]` | Start the web workbench (creates the problem if needed). | +| `cf doctor` | Check compiler, shim, `timeout`, Node and the build cache. | +| `cf clean` | Remove the build cache. | +| `cf update` | Fast-forward to the latest version (see [`docs/UPDATE_COMMAND.md`](docs/UPDATE_COMMAND.md)). | -| Command | Description | -| -------------------- | -------------------------------------------------------------------------------------------------- | -| `cf template ` | Scaffold `src//` with a solution and sample files. | -| `cf [input]` | Compile and run a solution with file or inline input. | -| `cf test` | Run a solution against its sample cases with timeout protection. | -| `cf serve [problem]` | Start the web workbench. | -| `cf update` | Pull the latest toolkit and re-run setup (see [`docs/UPDATE_COMMAND.md`](docs/UPDATE_COMMAND.md)). | +Run `cf help` for every option and environment variable. --- ## Contributing Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for details. +`make check` runs the same gate as CI. ## License diff --git a/SECURITY.md b/SECURITY.md index 9a9d6c6..536398d 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,16 +1,43 @@ # Security Policy -## Supported Versions +## Scope -We recommend always using the latest version of the toolkit. +`cf` is a local development tool. The web workbench binds to `localhost` and +compiles and executes C++ that you type into it with your own user account; +it is not designed to be exposed to untrusted users or the public internet. +Within that model the following protections are in place: -| Version | Supported | -| ------- | ------------------ | -| 1.x | :white_check_mark: | -| < 1.0 | :x: | +- Compiler flags supplied from the browser are validated against an + allowlist; flags that write files, add search paths, load plugins or pass + through to the linker are rejected and reported (`web/app/api/_engine/flags.ts`). +- The `/api/solution` and `/api/problem-text` routes accept only a single + safe path segment as the problem name, so requests cannot read or write + outside the problems directory. +- Sources, stdin and captured output are size-capped, compilation and + execution are time-limited, and every build runs in its own temporary + directory. +- The problem store writes atomically and never evaluates stored content. -## Reporting a Vulnerability +Do not run the workbench on a shared host or reverse-proxy it to the +internet; the programs it runs are not sandboxed beyond the operating +system's normal process isolation. -If you discover a potential security vulnerability, please do not open a public issue. Instead, please report it privately to [INSERT EMAIL/CONTACT]. +## Supported versions -We will acknowledge your report within 48 hours and provide a timeline for a fix if necessary. +Only the latest release receives fixes. + +| Version | Supported | +| ------- | --------- | +| 1.x | yes | +| < 1.0 | no | + +## Reporting a vulnerability + +Please do not open a public issue for a security problem. Use GitHub's +private vulnerability reporting on this repository +(**Security -> Report a vulnerability**) so the report stays confidential +until a fix is available. Include the version (`cf version` or the Settings +Server block), the platform, and steps to reproduce. + +Reports are acknowledged within a few days, and a fix or mitigation is +released as soon as one is ready. diff --git a/docs/README.md b/docs/README.md index 13c0a0b..bb0b17e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,36 +1,39 @@ # cf Workbench documentation -This folder documents the `cf` workbench as a stable release. Start with the -[project README](../README.md) for a high-level tour, then dive into the topic -you need below. +This folder documents the `cf` workbench. Start with the +[project README](../README.md) for a high-level tour, then dive into the +topic you need below. ## Contents -| Document | What it covers | -| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -| [features.md](features.md) | Every panel and setting in the browser workbench, plus the keyboard shortcuts and what persists across reloads. | -| [architecture.md](architecture.md) | The server execution engine (`web/app/api/_engine`), the macOS `` shim, the comparison logic, and the JSON problem store. | -| [api.md](api.md) | The request / response contract for every `/api/*` route. | -| [development.md](development.md) | Prerequisites, install, run, build, lint, the `Makefile`, and the Playwright end-to-end suite. | -| [troubleshooting.md](troubleshooting.md) | Compiler not found, time limits, output caps, and macOS / bash 3.2 notes. | -| [UPDATE_COMMAND.md](UPDATE_COMMAND.md) | The `cf update` CLI command. | +| Document | What it covers | +| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| [features.md](features.md) | Every panel and setting in the browser workbench, every CLI command, the keyboard shortcuts and what persists across reloads. | +| [architecture.md](architecture.md) | The server execution engine (`web/app/api/_engine`), the compile cache, the checkers, the statement parser, the macOS shim and the JSON store. | +| [api.md](api.md) | The request / response contract for every `/api/*` route. | +| [development.md](development.md) | Prerequisites, install, run, build, the quality gates, the unit / CLI / Playwright suites, CI and releasing. | +| [troubleshooting.md](troubleshooting.md) | Compiler not found, ignored flags, time limits, output caps, checkers, the cache, Windows and macOS notes. | +| [UPDATE_COMMAND.md](UPDATE_COMMAND.md) | The `cf update` CLI command. | +| [../CHANGELOG.md](../CHANGELOG.md) | Release history. | ## What `cf` is -`cf` is a local-first workbench for competitive programming in C++. A Next.js app -under `web/` provides the editor and panels; a server-side engine compiles and runs -your code against the real C++ toolchain installed on the machine. Nothing is sent -to a remote service. +`cf` is a local-first workbench for competitive programming in C++. A +Next.js app under `web/` provides the editor and panels, a Bash CLI under +`scripts/` covers the terminal workflow, and a server-side engine compiles +and runs your code against the real C++ toolchain installed on the machine. +Nothing is sent to a remote service. -The defining design choice is portability across toolchains. Every compile passes -`-std=gnu++17 -O2 -I include`, and the bundled [`include/bits/stdc++.h`](../include/bits/stdc++.h) -shim makes the ubiquitous `#include ` idiom resolve on Apple clang -and libc++ (macOS) as well as g++ and libstdc++ (Linux / WSL). +The defining design choice is portability across toolchains. Every compile +passes `-std=gnu++17 -O2 -I include`, and the bundled +[`include/bits/stdc++.h`](../include/bits/stdc++.h) shim makes the ubiquitous +`#include ` idiom resolve on Apple clang and libc++ (macOS) as +well as g++ and libstdc++ (Linux / WSL) and MinGW or LLVM on Windows. ## Conventions in these docs - Paths are relative to the repository root unless noted. - "The engine" means the shared module at `web/app/api/_engine/`. -- Request / response field names are quoted verbatim from the route source so the - docs stay in lockstep with the implementation. +- Request / response field names are quoted verbatim from the route source so + the docs stay in lockstep with the implementation. - This project does not use emojis in documentation. diff --git a/docs/UPDATE_COMMAND.md b/docs/UPDATE_COMMAND.md index c0f0bf9..80ad5f4 100644 --- a/docs/UPDATE_COMMAND.md +++ b/docs/UPDATE_COMMAND.md @@ -20,10 +20,13 @@ cf update ### Update Process -1. Fetches latest changes from `origin` remote -2. Pulls changes from `origin/main` branch -3. Runs setup script automatically (if it exists) -4. Returns to original directory +1. Fetches latest changes from the tracked remote (`origin` by default) +2. Fast-forwards the current branch (`git pull --ff-only`); a diverged + branch is reported instead of being merged +3. Runs the setup script automatically (skip with `CF_UPDATE_SKIP_SETUP=1`) +4. Returns to the original directory + +Run `cf version` afterwards to confirm the new version. ### Error Handling diff --git a/docs/api.md b/docs/api.md index 2937670..fde01b1 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,12 +1,12 @@ # API reference -The workbench's server API lives under `web/app/api/`. Every route runs on the Node -runtime (`runtime = "nodejs"`) and is dynamic (`dynamic = "force-dynamic"`). Field -names below are transcribed from each route's source and its top-of-file contract -comment; the typed client in `web/lib/api.ts` mirrors them. +The workbench's server API lives under `web/app/api/`. Every route runs on the +Node runtime (`runtime = "nodejs"`) and is dynamic (`dynamic = "force-dynamic"`). +Field names below are transcribed from each route's source and its top-of-file +contract comment; the typed client in `web/lib/api.ts` mirrors them. -All request bodies are JSON. On a bad request a route replies with a non-2xx status -and a body of `{ "error": string }`. +All request bodies are JSON objects. On a bad request a route replies with a +non-2xx status and a body of `{ "error": string }`; malformed JSON is a 400. ## Summary @@ -15,91 +15,138 @@ and a body of `{ "error": string }`. | `/api/run` | POST | Compile a source and run it once. | | `/api/test` | POST | Compile once, run against many cases, return AC/WA/TLE/RE/CE. | | `/api/stress` | POST | Compare a solution to a brute force on generated inputs. | -| `/api/problems` | GET / POST / DELETE | CRUD for saved problems. | -| `/api/config` | GET | Read environment-provided hints. | -| `/api/problem-text` | GET / POST | Load / save a problem statement (`problem.txt`). | -| `/api/solution` | GET / POST | Load / save a solution (`solution.cpp`). | -| `/api/template` | POST | Fetch a named starter template. | +| `/api/samples` | POST | Parse a pasted statement into sample tests and limits. | +| `/api/problems` | GET / POST / DELETE | CRUD, duplicate, export and import for saved problems. | +| `/api/config` | GET / DELETE | Toolchain facts and limits; clear the compile cache. | +| `/api/template` | GET / POST | List / fetch the bundled starter templates. | +| `/api/problem-text` | GET / POST | Load / save a problem statement (`problem.txt`) in `src/`. | +| `/api/solution` | GET / POST | Load / save a solution (`solution.cpp`) in `src/`. | -The shared compile/run parameters -- `code`, `timeLimitMs`, `std`, `compilerFlags` -- -behave identically everywhere: `std` defaults to `gnu++17`, `timeLimitMs` defaults to -5000 and is clamped to `[100, 60000]`, and `compilerFlags` is a string array appended -to the compile command. +### Shared compile / run options + +`/api/run`, `/api/test` and `/api/stress` accept the same option fields: + +| Field | Type | Default | Notes | +| --------------- | -------- | --------- | ---------------------------------------------------------------------- | +| `std` | string | `gnu++17` | Validated; unknown standards fall back to the default. | +| `timeLimitMs` | number | `5000` | Clamped to `[100, 60000]`. | +| `compilerFlags` | string[] | `[]` | Validated against an allowlist; rejected flags are reported. | +| `checker` | string | `lines` | `lines`, `tokens` or `float` (see [architecture.md](architecture.md)). | +| `epsilon` | number | `1e-6` | Float checker tolerance, clamped to `[0, 1]`. | + +### Shared compile block + +Every compile reports the same `compile` object: + +```json +{ + "ok": true, + "stderr": "", + "ms": 912, + "cached": false, + "diagnostics": [ + { + "severity": "error", + "line": 5, + "column": 9, + "message": "'x' was not declared in this scope" + } + ], + "rejectedFlags": [{ "flag": "-o", "reason": "flag family is not allowed" }] +} +``` + +`ms` is 0 and `cached` is true when the binary was served from the compile +cache. `diagnostics[].line` is `null` for diagnostics that point at another +file (for example the bundled shim). Sources larger than 1 MiB are refused +with `error: "source-too-large"`; compilation is killed after 30 s. --- ## POST /api/run -Compile a single C++ source and run it once with the given stdin. +Compile a single C++ source and run it once. -### Request +Request: ```json { - "code": "string (required)", - "input": "string (stdin; `stdin` is accepted as an alias)", - "timeLimitMs": 5000, + "code": "#include ...", + "input": "2 3\n", + "timeLimitMs": 2000, "std": "gnu++17", "compilerFlags": ["-Wall"] } ``` -`code` is required and must be non-empty (otherwise 400). `input` and `stdin` are -interchangeable. +`stdin` is accepted as an alias for `input`. `code` is required. -### Response (200) +Response (200): ```json { "ok": true, "verdict": "OK", - "stdout": "string", - "stderr": "string", + "stdout": "5\n", + "stderr": "", "exitCode": 0, "signal": null, "timedOut": false, - "timeMs": 1.83, + "timeMs": 3.2, "truncated": false, "compiler": "clang++", - "compile": { "ok": true, "stderr": "", "ms": 412.5 } + "compile": { + "ok": true, + "stderr": "", + "ms": 912, + "cached": false, + "diagnostics": [], + "rejectedFlags": [] + } } ``` -- `verdict` is one of `OK`, `CE`, `RE`, `TLE`; `ok` is `verdict === "OK"`. -- A compile failure returns 200 with `verdict: "CE"`, `compile.ok: false`, and the - diagnostics in `compile.stderr` (also mirrored to the top-level `stderr`). -- `stdout`, `stderr`, and `exitCode` are preserved as top-level fields for backward - compatibility. +`verdict` is `OK`, `CE` (compile failed; `compile.stderr` holds the text), +`RE` (non-zero exit, signal or spawn failure) or `TLE` (killed at the time +limit). `timeMs` is wall-clock time measured in Node. `truncated` is set when +stdout or stderr hit the 4 MiB cap. --- ## POST /api/test -Compile the source once and run it against an array of cases. The binary is reused -across cases. +Compile once and run against many cases. -### Request +Request: ```json { - "code": "string (required)", + "code": "...", "tests": [{ "input": "2 3\n", "expected": "5\n" }], - "timeLimitMs": 5000, - "std": "gnu++17", - "compilerFlags": [] + "checker": "tokens", + "stopOnFirstFailure": false } ``` -`tests` must contain at least one case (otherwise 400). +`code` and a non-empty `tests` array (at most 200 cases) are required. -### Response (200) +Response (200): ```json { "ok": true, "compiler": "clang++", - "compile": { "ok": true, "stderr": "", "ms": 408.1 }, - "summary": { "total": 1, "passed": 1, "failed": 0, "verdict": "AC" }, + "compile": { "...": "..." }, + "checker": "tokens", + "summary": { + "total": 2, + "passed": 2, + "failed": 0, + "skipped": 0, + "verdict": "AC", + "maxTimeMs": 4.1, + "totalTimeMs": 7.9 + }, "results": [ { "index": 0, @@ -110,182 +157,204 @@ across cases. "stderr": "", "exitCode": 0, "signal": null, - "timeMs": 1.2, + "timeMs": 3.8, "truncated": false, + "presentationOnly": false, "diff": [] } ] } ``` -- Per-case `verdict` is one of `AC`, `WA`, `TLE`, `RE`, `CE`. -- `diff` is populated only for `WA`: an array of `{ line, expected, actual, same }` - for the differing lines (capped at 200), under whitespace-tolerant comparison. -- `summary.verdict` is the worst verdict across all cases (ordered - `AC < WA < TLE < RE < CE`); `ok` is true only when every case is `AC`. -- On a compile failure the route returns 200 with every case marked `CE` (so the UI - can badge each row) and `summary.verdict: "CE"`. +Per-case `verdict` is `AC`, `WA`, `TLE`, `RE`, `CE` (every case, when the +compile fails) or `SKIPPED` (after a failure with `stopOnFirstFailure`). +`summary.verdict` is the worst verdict in `CE > RE > TLE > WA > AC` order. +`diff` lists only the differing lines (capped at 200) as +`{ line, expected, actual, same }`, with `null` for a missing side. +`presentationOnly` is true for a `WA` under the `lines` checker whose tokens +match. --- ## POST /api/stress -Compile a solution, a brute force, and a generator, then loop: run `generator ` -to produce an input, feed it to both programs, and compare their outputs. Returns the -first input on which they disagree (or on which a program crashes or times out). +Compile three sources and search for a counter-example. -### Request +Request: ```json { - "solution": "string (required)", - "brute": "string (required)", - "generator": "string (required)", - "iterations": 100, - "timeLimitMs": 5000, + "solution": "...", + "brute": "...", + "generator": "...", + "iterations": 200, "seedBase": 1, - "std": "gnu++17" + "timeLimitMs": 2000, + "checker": "lines" } ``` -- All three sources are required and must be non-empty (otherwise 400). -- `iterations` defaults to 100 and is clamped to `[1, 5000]`. -- `seedBase` defaults to 1; iteration `i` uses seed `seedBase + i`, passed to the - generator as `argv[1]`. +All three sources are required. `iterations` is clamped to `[1, 5000]`; the +generator receives `seedBase + i` as `argv[1]`. -### Response (200) +Response (200): ```json { "ok": true, "failed": true, - "iterationsRun": 37, + "iterationsRun": 7, + "elapsedMs": 412, + "deadlineHit": false, + "stats": { "maxSolutionMs": 5.2, "maxBruteMs": 4.9, "avgSolutionMs": 3.1 }, "compile": { - "solution": { "ok": true, "stderr": "", "ms": 410 }, - "brute": { "ok": true, "stderr": "", "ms": 405 }, - "generator": { "ok": true, "stderr": "", "ms": 398 } + "solution": { + "ok": true, + "stderr": "", + "ms": 900, + "cached": false, + "diagnostics": [] + }, + "brute": { "...": "..." }, + "generator": { "...": "..." } }, "firstFailure": { - "iteration": 37, - "seed": 37, + "iteration": 7, + "seed": 7, "reason": "mismatch", - "input": "...", - "solutionOutput": "...", - "bruteOutput": "...", - "diff": [{ "line": 1, "expected": "...", "actual": "...", "same": false }] + "input": "615 892\n", + "solutionOutput": "1506\n", + "bruteOutput": "1507\n", + "diff": [{ "line": 1, "expected": "1507", "actual": "1506", "same": false }] }, - "message": "Found a counter-example on iteration 37." + "message": "Found a counter-example on iteration 7." } ``` -- `ok` means the three sources compiled and the loop ran -- not that the solution is - correct. `failed` is true when a counter-example was found. -- `firstFailure.reason` is one of `mismatch`, `solution-error`, `brute-error`, - `solution-tle`, `brute-tle`, `generator-error`. Depending on the reason, the - failure may also carry `generatorStderr`, `solutionStderr`, or `bruteStderr`. -- If a source fails to compile, the route returns 200 with `ok: false`, - `failed: false`, and the offending `compile..stderr` populated. -- The loop is bounded by an overall 30-second deadline, so `iterationsRun` may be less - than the requested `iterations`. +`ok` means the sources compiled and the search ran; `failed` means a +counter-example was found. `reason` is one of `mismatch`, `solution-error`, +`brute-error`, `solution-tle`, `brute-tle`, `generator-error`; the error +variants also carry `solutionStderr`, `bruteStderr` or `generatorStderr`. A +compile failure returns `ok: false` with the offending `compile.*.stderr` +populated. The request is bounded by a 60 s budget; `deadlineHit` says when +that cut the search short. --- -## /api/problems - -CRUD for saved problems, persisted as `web/data/problems/.json`. A `slug` is -derived from a problem's `name` and is the stable key; saving with the same name -updates the existing record. Routes accept either an exact slug or a human name and -resolve it with `slugify`. +## POST /api/samples -### GET /api/problems +Parse a pasted statement. -- `GET /api/problems` returns `{ "problems": ProblemSummary[] }`, newest first. - `ProblemSummary = { name, slug, testCount, updatedAt }`. -- `GET /api/problems?name=` returns `{ "problem": Problem }`, or 404 with - `{ "error": string }` if not found. +Request: `{ "statement": "A. Team\ntime limit per test\n2 seconds\n...Examples\nInput\n...\nOutput\n..." }` -`Problem = { name, slug, code, statement, tests: { input, expected }[], createdAt, updatedAt }`. - -### POST /api/problems - -Two shapes: +Response (200): ```json -{ "name": "Two Sum", "code": "...", "statement": "...", "tests": [ ... ] } -``` - -upserts a problem and returns `{ "ok": true, "problem": Problem }`. `name` is -required; `code`, `statement`, and `tests` are optional and preserved from the -existing record when omitted. - -```json -{ "op": "rename", "from": "", "to": "New Name" } +{ + "title": "A. Team", + "timeLimitMs": 2000, + "memoryLimitMb": 256, + "tests": [{ "input": "3\n1 1 0\n1 1 1\n1 0 0\n", "expected": "2\n" }] +} ``` -renames a problem and returns `{ "ok": true, "problem": Problem }`, or 404 if the -source does not exist. - -### DELETE /api/problems - -`DELETE /api/problems?name=` removes the problem and returns -`{ "ok": true, "deleted": boolean }`. `deleted` is false when no matching file existed. +Headings are matched case-insensitively (`Examples`, `Example`, `Sample 1`, +`Input`, `Output`, `Sample Input N`, `Sample Output N`, `inputCopy`); a +`Note` heading ends the examples. A statement without samples yields +`tests: []`. --- -## GET /api/config - -Returns environment-provided hints for the UI: - -```json -{ "startProblem": "1000A or null", "problemsDir": "/path or null" } -``` +## /api/problems -Backed by `CF_START_PROBLEM` and `CF_PROBLEMS_DIR`. +Saved problems are JSON files under `web/data/problems/.json` (or +`$CF_DATA_DIR/problems`). `slug` is derived from `name` (lowercase, +non-alphanumerics become `-`) and is the stable key; saving with the same +name updates the record. Writes are atomic. ---- +Types: -## /api/problem-text +```ts +TestCase = { input: string; expected: string } +ProblemSettings= { std?, timeLimitMs?, compilerFlags?, checker?, epsilon? } +Problem = { name, slug, code, statement, tests: TestCase[], stdin, + brute, generator, settings: ProblemSettings | null, + createdAt, updatedAt } +ProblemSummary = { name, slug, testCount, updatedAt } +``` -Per-problem statement stored as `src//problem.txt`. +| Request | Response | +| ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | +| `GET /api/problems` | `{ problems: ProblemSummary[] }` newest first | +| `GET /api/problems?name=` | `{ problem: Problem }` or 404 | +| `GET /api/problems?export=1` | `{ version: 1, exportedAt, problems: Problem[] }` | +| `POST /api/problems { name, code?, statement?, tests?, stdin?, brute?, generator?, settings? }` | `{ ok, problem }` (upsert; omitted fields keep their stored value) | +| `POST /api/problems { op: "rename", from, to }` | `{ ok, problem }` or 404 | +| `POST /api/problems { op: "duplicate", from, to }` | `{ ok, problem }` or 404 | +| `POST /api/problems { op: "import", problems: Problem[], overwrite? }` | `{ ok, imported, skipped }` | +| `DELETE /api/problems?name=` | `{ ok: true, deleted: boolean }` | -- `GET /api/problem-text?problem=` returns `{ "text": string }` (empty string - when absent). Missing `problem` is a 400. -- `POST /api/problem-text` with `{ "problem", "text" }` returns `{ "success": true }`. +Names are limited to 120 characters. Import skips problems that already exist +unless `overwrite` is true, and tolerates records written by older versions. --- -## /api/solution +## /api/config -Per-problem solution stored as `src//solution.cpp`. +`GET /api/config` returns facts the UI shows in Settings: -- `GET /api/solution?problem=` returns `{ "code": string }` (empty string when - absent). Missing `problem` is a 400. -- `POST /api/solution` with `{ "problem", "code" }` returns `{ "success": true }`. - A write is skipped when the content is unchanged. +```json +{ + "version": "1.0.0", + "platform": "darwin", + "compiler": "clang++", + "compilerVersion": "Apple clang version 16.0.0 (clang-1600.0.26.4)", + "includeDir": "/path/to/cf/include", + "defaults": { + "std": "gnu++17", + "timeLimitMs": 5000, + "maxTimeLimitMs": 60000, + "checker": "lines" + }, + "limits": { + "maxSourceBytes": 1048576, + "maxInputBytes": 4194304, + "maxOutputBytes": 4194304, + "compileTimeoutMs": 30000 + }, + "cache": { "entries": 3 }, + "startProblem": null, + "problemsDir": null +} +``` ---- +`startProblem` and `problemsDir` echo `CF_START_PROBLEM` / `CF_PROBLEMS_DIR` +as set by `cf serve`. `DELETE /api/config?cache=1` drops every cached binary +and returns `{ ok: true, cleared: }`. -## POST /api/template +--- -Fetch a named starter template from `templates/.cpp`. +## /api/template -### Request +- `GET /api/template` returns `{ "templates": ["dp", "graph", "math"] }`, the + `.cpp` files under the repository `templates/` directory. +- `POST /api/template` with `{ "name": "dp" }` returns + `{ stdout, stderr, exitCode, content }`; `stdout` and `content` both hold the + template text (`stdout` is kept for backward compatibility). A miss returns + `exitCode: 1` and an error in `stderr`. -```json -{ "name": "dp" } -``` +## /api/problem-text and /api/solution -### Response (200) +These read and write `problem.txt` / `solution.cpp` under `src//` +(or `$CF_PROBLEMS_DIR//`) for CLI interoperability. -```json -{ - "stdout": "