Skip to content
Open
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
127 changes: 84 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,43 +1,84 @@
# Candidate Assessment: Spec-Driven Development With Codegen Tools

This assessment evaluates how you use modern code generation tools (for example `5.2-Codex`, `Claude`, `Copilot`, and similar) to design, build, and test a software application using a spec-driven development pattern. You may build a frontend, a backend, or both.

## Goals
- Build a working application with at least one meaningful feature.
- Create a testing framework to validate the application.
- Demonstrate effective use of code generation tools to accelerate delivery.
- Show clear, maintainable engineering practices.

## Deliverables
- Application source code in this repository.
- A test suite and test harness that can be run locally.
- Documentation that explains how to run the app and the tests.

## Scope Options
Pick one:
- Frontend-only application.
- Backend-only application.
- Full-stack application.

Your solution should include at least one real workflow, for example:
- Create and view a resource.
- Search or filter data.
- Persist data in memory or storage.

## Rules
- You must use a code generation tool (for example `5.2-Codex`, `Claude`, or similar). You can use multiple tools.
- You must build the application and a testing framework for it.
- The application and tests must run locally.
- Do not include secrets or credentials in this repository.

## Evaluation Criteria
- Working product: Does the app do what it claims?
- Test coverage: Do tests cover key workflows and edge cases?
- Engineering quality: Clarity, structure, and maintainability.
- Use of codegen: How effectively you used tools to accelerate work.
- Documentation: Clear setup and run instructions.

## What to Submit
- When you are complete, put up a Pull Request against this repository with your changes.
- A short summary of your approach and tools used in your PR submission
- Any additional information or approach that helped you.
# Spec-Driven Task App

This repository contains a small spec-driven full-stack example: a FastAPI backend and a plain HTML + JavaScript frontend for managing tasks in memory.

Project structure
```
backend/
main.py
models.py
routes.py
storage.py
frontend/
index.html
tests/
test_tasks.py
requirements.txt
README.md
```

Quickstart

1. Install dependencies (recommended in a virtualenv):

```bash
pip install -r requirements.txt
```

2. Run the backend:

```bash
python -m backend.main
```

The API will be available at `http://127.0.0.1:8000` and the OpenAPI docs at `http://127.0.0.1:8000/docs`.

# Spec-Driven Task Manager

This repository contains a small full-stack Task Manager implemented using a spec-driven approach.

Task spec (single source of truth):

{
"id": "uuid",
"title": "string",
"description": "string",
"status": "pending | completed"
}

## Run the backend

Install dependencies into a virtualenv, then run with Uvicorn:

```bash
python -m pip install -r requirements.txt
uvicorn backend.main:app --reload --port 8000
```

The backend serves the API and the frontend. Open http://localhost:8000/ to use the web UI.

API endpoints:
- `POST /tasks` — create a task (JSON: title, description)
- `GET /tasks` — list tasks
- `GET /tasks?status=pending|completed` — filter tasks by status
- `PATCH /tasks/{id}/status` — update a task's status (JSON: {status: "completed"})

Test-only endpoint:
- `POST /test/clear` — clears in-memory storage (used by the test suite)

## Frontend

Open http://localhost:8000/ in your browser. The single `index.html` page allows creating tasks, filtering, and toggling status.

## Run tests

```bash
python -m pip install -r requirements.txt
pytest -q
```

## How AI / Code generation tools were used

- The project was implemented following a spec-driven workflow. Pydantic models were derived from the task spec and used for validation and OpenAPI generation.
- Code generation tools were used to scaffold boilerplate (models, routes, storage) and to iterate on API shapes and tests, accelerating implementation while preserving human review and refinement.

1 change: 1 addition & 0 deletions backend/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# backend package
Binary file added backend/__pycache__/__init__.cpython-312.pyc
Binary file not shown.
Binary file added backend/__pycache__/main.cpython-312.pyc
Binary file not shown.
Binary file added backend/__pycache__/models.cpython-312.pyc
Binary file not shown.
Binary file added backend/__pycache__/routes.cpython-312.pyc
Binary file not shown.
Binary file added backend/__pycache__/storage.cpython-312.pyc
Binary file not shown.
29 changes: 29 additions & 0 deletions backend/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from .routes import router

app = FastAPI(title="Spec-Driven Task Manager", version="0.1")
app.include_router(router)

# Serve the frontend folder at the app root (index.html will be served)
app.mount("/", StaticFiles(directory="frontend", html=True), name="frontend")
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from .routes import router

app = FastAPI(title="Task API")
app.include_router(router)

app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)


if __name__ == "__main__":
import uvicorn

uvicorn.run("backend.main:app", host="0.0.0.0", port=8000, reload=True)
31 changes: 31 additions & 0 deletions backend/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from pydantic import BaseModel
from typing import Literal


class TaskBase(BaseModel):
title: str
description: str


class TaskCreate(TaskBase):
pass


class Task(TaskBase):
id: str
status: Literal["pending", "completed"]
from pydantic import BaseModel, Field
from typing import Optional, Literal
from uuid import UUID


class Task(BaseModel):
id: UUID
title: str = Field(..., min_length=1)
description: Optional[str] = ""
status: Literal["pending", "completed"] = "pending"


class TaskCreate(BaseModel):
title: str = Field(..., min_length=1)
description: Optional[str] = ""
38 changes: 38 additions & 0 deletions backend/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from fastapi import APIRouter, HTTPException
from typing import List, Optional

from . import storage
from .models import TaskCreate, Task

router = APIRouter()


@router.post("/tasks", response_model=Task, status_code=201)
def create_task(payload: TaskCreate):
task = storage.create_task(payload.title, payload.description)
return task


@router.get("/tasks", response_model=List[Task])
def get_tasks(status: Optional[str] = None):
if status and status not in ("pending", "completed"):
raise HTTPException(status_code=400, detail="invalid status")
return storage.list_tasks(status)


@router.patch("/tasks/{task_id}/status", response_model=Task)
def patch_status(task_id: str, payload: dict):
status = payload.get("status")
if status not in ("pending", "completed"):
raise HTTPException(status_code=400, detail="invalid status")
task = storage.update_status(task_id, status)
if not task:
raise HTTPException(status_code=404, detail="not found")
return task


# Test-only convenience endpoint to reset in-memory state
@router.post("/test/clear", status_code=204)
def test_clear():
storage.clear_storage()
return
45 changes: 45 additions & 0 deletions backend/storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""In-memory, thread-safe storage for tasks.

This module stores tasks as dictionaries with string UUID ids. It provides
thread-safe functions matching the API expectations used in the tests and
routes: `create_task`, `list_tasks`, `update_status`, and `clear_storage`.
"""
from typing import Dict, List, Optional
import threading
import uuid

_lock = threading.Lock()
_tasks: Dict[str, Dict] = {}


def clear_storage() -> None:
with _lock:
_tasks.clear()


def create_task(title: str, description: str) -> Dict:
with _lock:
task_id = str(uuid.uuid4())
task = {"id": task_id, "title": title, "description": description, "status": "pending"}
_tasks[task_id] = task
return task.copy()


def list_tasks(status: Optional[str] = None) -> List[Dict]:
with _lock:
items = list(_tasks.values())
if status:
items = [t.copy() for t in items if t["status"] == status]
else:
items = [t.copy() for t in items]
return items


def update_status(task_id: str, status: str) -> Optional[Dict]:
if status not in ("pending", "completed"):
raise ValueError("invalid status")
with _lock:
if task_id not in _tasks:
return None
_tasks[task_id]["status"] = status
return _tasks[task_id].copy()
Loading