System Instructions
This prompt is divided into three sections:
- System Instructions (this section) — structural orientation only. Do not treat this
section as task input.
- Input Context — begins with the heading
# Input Context. All blocks are wrapped in
<pblock> tags. Two block types:
- File blocks:
<pblock filename="<name>" role="<role>" guidance="...">— optional
guidance attribute carries context-specific instructions; content is in a fenced block.
- Metadata/section blocks:
<pblock label="<label>" kind="<kind>">— job parameters,
rules, instructions, or group headers.
- Agent Task — begins with the heading
# Agent Task. Defines your persona, constraints,
and required outputs. Read all input context before acting on this section.
Input Context
<pblock label="Build block job" kind="job">
Build block job
- TARGET: ReadingList
- BUILD_DIRECTORY: /mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/build/ReadingList
- WORKING_DIRECTORY: /mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/build/ReadingList
- BUILD_BLOCK: Block 1 · Foundational (block-1)
- STORIES: Establish the Flask application architecture and module boundaries. (architecture)
- DATE: 2026-08-15
- BUILD_SCOPE: exactly one MANIFEST.md build block
</pblock>
<pblock label="Stories in this block" kind="section">
Stories in this block
- Establish the Flask application architecture and module boundaries. (architecture) [story]
</pblock>
COMPASS - Target Orientation
<pblock filename="COMPASS.md" role="compass" path="/mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/workspace/targets/ReadingList/COMPASS.md" guidance="Important: This is the core project intent, constraints, and guardrails. It should have presecedence in conflicts.">
# COMPASS: ReadingList
## Compass
ReadingList is a small web application for readers who want to maintain a personal, ordered list of books to read. A reader can add a book with its title and author, review books in insertion order, and remove books when they are no longer needed.
## Constraints
- The application must run as a web application.
- Book submissions require both a title and an author.
- Books must be shown in the order they were added.
- `bin/test.sh` must be POSIX-compatible and run from the application root.
- The complete automated test suite must pass for `bin/test.sh` to exit zero.
## Guardrails
- Never accept or store a submission with an empty title or author.
- Never reorder books relative to their addition order.
- Never report the test command as successful when any automated test fails.
- Always provide a clear reason when rejecting incomplete input.
<!-- drydock:build-write-guardrail:start -->
## Build Write Guardrail
- Authorized build directory: `/mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/build/ReadingList`
- Authorized Target directory: `/mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/workspace/targets/ReadingList`
- Build agents have permission to create, modify, and remove files required by the active build block inside these authorized directories.
- No path outside these authorized directories may be modified.
- Protected Drydock artifacts:
- `/mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/workspace/targets/ReadingList/blueprint/`
- `/mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/workspace/targets/ReadingList/MANIFEST.md`
- `/mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/workspace/targets/ReadingList/COMPASS.md`
- `/mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/workspace/targets/ReadingList/QuarterDeck/`
- `/mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/workspace/targets/ReadingList/evidence/`
<!-- drydock:build-write-guardrail:end -->
<!-- Drydock author intent sha256=c86a1a56f7d25ca5548192dbe12c57989f5b1ddd34835753e64e7deb83845e3a source=reading-list.md -->
# Reading List
Build a web application that keeps a list of books to read.
The reader can add a book with a title and author, view the books in the order added,
and remove a book. An empty title or author is rejected with a clear error message.
The application includes automated tests for each behavior.
The completed application provides a POSIX-compatible `bin/test.sh` that runs the complete
automated test suite from the application root. `sh bin/test.sh` exits zero only when every test
passes. The final build story runs this command after every implementation story and preserves its
command, exit code, standard output, and standard error as evidence.
</pblock>
STACK - Technology HOW
<pblock filename="python.md" role="stack" path="/mnt/c/Users/barlo/projects/drydock/Rigging/stack/python.md">
# Python Best Practices
**Version:** 20260716 V3
**Category:** Technologies
**Description:** Python language conventions and patterns for specification-driven projects
Technology reference for Python development. Framework-agnostic — applies to any Python project. This file does not change between projects.
Prerequisite: `stack/common.md`
---
## 1. Configuration Management
**Rule**: All environment access goes through one typed `Config` class (see `stack/persistence.md`) — never read `os.environ` elsewhere. Every field is inherited from the environment; there are no `Dev`/`Prod`/`Test` subclasses. Never hardcode secrets, ports, or paths.
```python
# config.py
import os
from dataclasses import dataclass
from dotenv import load_dotenv
load_dotenv()
@dataclass(frozen=True)
class Config:
secret_key: str
database_path: str
port: int
debug: bool = False
@classmethod
def load(cls) -> "Config":
try:
return cls(
secret_key=os.environ["SECRET_KEY"],
database_path=os.environ.get("DATABASE_PATH", "data/app.db"),
port=int(os.environ.get("APP_PORT", "5001")),
debug=os.environ.get("APP_DEBUG") == "1",
)
except KeyError as e:
raise RuntimeError(f"Missing required env var: {e}") from e
```
Commit `.env.example` listing every variable `Config` reads, with dummy values. The build creates the local `.env` from it; `.env` itself is never committed.
```bash
# .env.example — committed; every variable Config reads, dummy values only
SECRET_KEY=change-me
DATABASE_PATH=data/app.db
APP_PORT=5001
APP_DEBUG=0
```
Rules:
- `python-dotenv` is a runtime dependency in `pyproject.toml` — not a dev dependency. Nothing in the toolchain loads `.env` on its own: `python run.py`, `uv run`, and `pytest` all leave the environment untouched. The application loads its own configuration or it does not start.
- `load_dotenv()` runs at import of `config.py`, before any `Config.load()` call, so every entry point — `run.py`, `bin/start.sh`, a management command, a worker — reads the same `.env`.
- `.env.example` is committed and lists every variable `Config` reads, no more and no fewer. A variable missing from it never reaches the running application.
- The startup error names the missing variable (`Missing required env var: 'SECRET_KEY'`). A generic message such as `Invalid application configuration` tells the operator nothing and is a defect.
- The entry point must start on a clean shell with nothing exported by hand: `.env` plus the defaults in `Config` are the whole configuration.
**Why**: A single typed `Config` is the only env reader. Typed fields crash on a missing or malformed variable at startup, not at first use. The environment (`.env`) selects configuration — not a Python subclass. A `Config` that reads `os.environ` without loading `.env` passes every test that constructs the app with overrides, then fails on the operator's first real run.
---
## 2. Code Style and Understandability
**Rule**: Code must be understandable on its own through naming, structure, small focused units, explicit types, clear interfaces, appropriate abstractions, and tests. If a reader needs a comment to follow the mechanics, improve the code instead.
Rules:
- **Naming** — names state intent: `load_active_users()`, not `get_data()`; `retry_limit`, not `n`. No abbreviations a new reader must decode.
- **Structure** — modules have one responsibility each (see §10 layout); related code lives together; call depth stays shallow.
- **Small focused units** — functions do one thing at one level of abstraction; a function that needs a section comment ("# now validate…") is two functions.
- **Explicit types** — public interfaces are fully typed (§3); the signature answers "what goes in, what comes out" without reading the body.
- **Clear interfaces** — few parameters, typed returns, no boolean flags that change what a function fundamentally does, no output-by-mutation surprises.
- **Appropriate abstractions** — introduce a layer only to remove real duplication or isolate a boundary (DB, cloud, external API). No speculative generality.
- **Tests** — tests are the executable specification of behavior (§6); a behavior worth keeping is a behavior worth a test.
- Comments state constraints the code cannot express (invariants, external quirks, why-not-the-obvious-way) — never restate what the next line does.
**Why**: Code is read far more often than written. Every hour invested in clarity is repaid at each future read, debug, and review — including by the author six months later.
---
## 3. Type Hints and Static Typing
**Rule**: Use modern type hints on all public interfaces. Model data that crosses module or process boundaries with typed structures — dataclasses, `TypedDict`, or Pydantic models — never bare dicts or tuples of implicit shape. Run a static type checker when practical.
```python
from dataclasses import dataclass
@dataclass(frozen=True)
class User:
id: int
email: str
roles: list[str]
# Public interfaces are fully typed; modern syntax only
def load_users(db: Database, limit: int | None = None) -> list[User]: ...
# Boundary shapes are explicit types, never dict[str, Any]
def to_response(user: User) -> UserResponse: ...
```
Rules:
- Type every public function, method, and class attribute. Module-private helpers may omit hints when the types are obvious.
- Use modern syntax: built-in generics (`list[str]`, `dict[str, int]`) and unions (`X | None`) — never `typing.List`, `Optional`, or `Union`.
- Schemas, serializers, services, and data structures are typed classes where appropriate: frozen dataclasses for internal data, Pydantic models or `TypedDict` at serialization and validation boundaries.
- Never rely on implicit or ambiguous shapes — no bare `dict`, positional tuples, or `Any` crossing a module boundary. If a shape matters, give it a name and a type.
- Run a suitable static type checker when practical: `uv add --dev mypy` then `uv run mypy .` (pyright is an acceptable alternative). Run it alongside ruff and pytest in CI.
**Why**: Typed interfaces make wrong calls fail at check time instead of runtime, and named shapes document intent where docstrings drift. The type checker is the cheapest reviewer the project has.
---
## 4. Logging
**Rule**: Use Python's `logging` module with named loggers, never `print()`. Configure formatters and handlers at startup.
```python
import logging
import os
def setup_logging(level=None):
level = level or ('DEBUG' if os.getenv('APP_DEBUG') else 'INFO')
formatter = logging.Formatter(
'%(asctime)s %(name)s %(levelname)s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
console = logging.StreamHandler()
console.setFormatter(formatter)
root = logging.getLogger()
root.setLevel(level)
root.addHandler(console)
# File handler
os.makedirs('data/logs', exist_ok=True)
file_handler = logging.FileHandler('data/logs/app.log')
file_handler.setFormatter(formatter)
root.addHandler(file_handler)
```
```python
# In any module
import logging
logger = logging.getLogger(__name__)
logger.info('Server starting on port %s', port)
logger.error('Failed to connect: %s', err)
```
**Why**: Named loggers trace messages to source modules. Structured format enables log parsing.
---
## 5. Environment Separation
**Rule**: Maintain distinct `.env` files per environment; the same typed `Config` reads whichever `.env` is present. Never run debug mode in production.
| Setting | Dev | Test | Prod |
|---------|-----|------|------|
| APP_DEBUG | 1 | 0 | 0 |
| DATABASE_PATH | data/app.db | :memory: | data/app.db |
| SECRET_KEY | .env value | .env value | .env value (required) |
| LOGGING | DEBUG | WARNING | INFO |
**Why**: Environment separation lives in `.env` values, not Python config subclasses, so the same code path runs everywhere. This prevents dev shortcuts from reaching production.
---
## 6. Testing
**Rule**: Use `pytest` with fixtures. Isolate each test with a fresh database. Test at the boundary, not internals. Every Python project build must include a complete pytest suite regardless of whether the specification mentions tests — a project without tests does not satisfy ACTIVE conformity.
### Required test files
**`tests/conftest.py`** — fixtures shared across all test modules:
- `app` fixture: `create_app(TestConfig)` — in-memory DB, `TESTING=True`
- `client` fixture: `app.test_client()`
- `db` fixture: fresh `init_db(':memory:')` per test, yielded inside `app.app_context()`
```python
# tests/conftest.py
import pytest
@pytest.fixture
def app(monkeypatch):
monkeypatch.setenv("SECRET_KEY", "test")
monkeypatch.setenv("DATABASE_PATH", ":memory:")
from app import create_app
from config import Config
yield create_app(Config.load())
@pytest.fixture
def client(app):
return app.test_client()
@pytest.fixture
def db(app):
from db import Database
with app.app_context():
yield Database(':memory:')
```
**`tests/test_smoke.py`** — liveness checks:
- App factory returns a Flask app without error
- `GET /health` returns 200 and `{"status": "ok"}`
- Root route `GET /` returns 200
**`tests/test_routes.py`** — one test per registered route:
- Every `GET` page route: `assert response.status_code == 200`
- Every `POST` API route: assert status in `{200, 201, 204}` with a minimal valid payload
- HTMX routes: include `HX-Request: true` header; assert 200 and non-empty `response.data`
- Routes with `{id}` params: use a fixture-created record for the ID
**`tests/test_db.py`** — only if project has a DATABASE.md:
- Schema test: after `Database(path)` init, all expected tables exist (`SELECT name FROM sqlite_master WHERE type='table'`)
- Round-trip per major table: insert a minimal valid row, read it back, assert field values match
- FK enforcement: inserting a row with an invalid FK raises `IntegrityError` (requires `PRAGMA foreign_keys=ON`)
### Configuration
**`pytest.ini`** at project root:
```ini
[pytest]
testpaths = tests
addopts = -v
```
Add `pytest` to `pyproject.toml` dev dependencies (see §8).
### What not to test
- Third-party library internals (Flask, SQLite, HTMX)
- Configuration loading — tested implicitly by fixture startup
- Private helper functions — test through the public interface that uses them
**Why**: Fixtures ensure clean state per test. In-memory DB makes tests fast.
---
## 7. Security Basics
**Rule**: Validate all user input. Use parameterized queries exclusively. Never trust client data.
Checklist:
- Parameterized queries for all DB operations (`?` placeholders, never f-strings)
- `secure_filename()` for any file path from user input
- Length and type validation on inputs
- Secret key loaded from environment, not hardcoded in prod
- Never expose stack traces to end users
**Why**: These basics prevent the most common attack vectors with minimal effort.
---
## 8. Dependency Management (uv)
**Rule**: Use `uv` for venv creation and dependency management. `pyproject.toml` is the required manifest; `uv.lock` is committed.
```bash
uv venv # creates .venv/
uv add flask python-dotenv # add runtime deps → updates pyproject.toml + uv.lock
uv add --dev pytest ruff mypy # add dev deps
uv sync # install from uv.lock (standard clone setup)
uv sync --frozen # strict install (CI — fail if lock is stale)
```
```toml
# pyproject.toml
[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"flask>=3.1",
"python-dotenv>=1.0",
]
[project.optional-dependencies]
dev = ["pytest>=8.0", "ruff>=0.4", "mypy>=1.10"]
[tool.ruff]
line-length = 88
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]
[tool.ruff.format]
quote-style = "double"
```
Rules:
- Use `uv add` / `uv pip install` — never bare `pip install`
- Use `uv venv` — never `python -m venv`
- Commit `pyproject.toml` and `uv.lock`; `.venv/` is gitignored
- Keep runtime dependencies minimal; dev deps in `[project.optional-dependencies].dev`
- When migrating an existing project: `uv venv`, `uv pip install -r requirements.txt`, `uv lock`, commit `uv.lock`
**Why**: uv resolves and locks dependencies deterministically, eliminating "works on my machine" drift. `uv sync --frozen` in CI guarantees the exact locked versions are installed.
Full toolchain conventions (uv workflow, ruff rulesets, local/CI gates): see `stack/uv_ruff.md`.
---
## 9. Health Check and Startup Validation
**Rule**: Validate required config and DB connectivity at startup. Crash early on misconfiguration.
```python
def validate_startup(config: Config, db: Database):
"""Crash early on misconfiguration. Config.load() already validates required
env vars; here we confirm the database is reachable."""
try:
db.healthcheck() # runs SELECT 1 inside the Database class
except Exception as e:
raise RuntimeError(f'Database not accessible: {e}')
logger.info('Startup validation passed')
```
**Why**: Required env vars are validated when `Config.load()` constructs the typed config, so startup validation only needs to confirm connectivity. Catches misconfigurations immediately rather than at first user request.
---
## 10. Project Directory Layout (Python-specific)
Python web projects extend the common layout:
```
project-name/
├── app.py # Entry point / app factory
├── routes.py # Route handlers
├── models.py # Data models and type registries
├── db.py # Database class: typed tables (row dataclass + CRUD), connection, schema, migrations
├── ops.py # Business logic and operations
├── config.py # typed Config class — the only env reader (stack/persistence.md)
├── templates/ # Jinja2 or Django templates
│ ├── base.html
│ └── types/ # Type-specific partials
├── static/
│ ├── css/
│ └── js/
├── tests/
│ ├── conftest.py
│ └── test_*.py
├── bin/ # (from common.md)
├── data/ # (from common.md)
├── pyproject.toml # preferred dependency manifest
├── uv.lock # committed — reproducible install record
├── .env
├── .gitignore
└── CLAUDE.md # endpoints/bookmarks live in AGENTS.md — no Links.md
```
---
## Summary Checklist
- [ ] One typed `Config` class is the only env reader; no hardcoded secrets, no Dev/Prod/Test subclasses; `.env.example` maintained (`stack/env_variables_and_secrets.md`)
- [ ] Code understandable through naming, structure, small units, explicit types, clear interfaces, appropriate abstractions, and tests
- [ ] All persistence/services through typed classes (`stack/persistence.md`) — no raw SQL/`os.environ`/`open()`/SDK in app code
- [ ] Modern type hints on all public interfaces; typed schemas/serializers/services; no ambiguous shapes across boundaries; type checker run when practical
- [ ] Logging with named loggers, not `print()`
- [ ] Distinct dev/test/prod configs
- [ ] pytest with fixtures and isolated test DB
- [ ] Input validation, parameterized queries
- [ ] `uv` for venv + deps; `pyproject.toml` + `uv.lock` committed; `.venv/` gitignored
- [ ] Startup validation for required config
</pblock>
<pblock filename="flask.md" role="stack" path="/mnt/c/Users/barlo/projects/drydock/Rigging/stack/flask.md">
# Flask Best Practices
**Version:** 20260410 V2
**Category:** Web Server
**Description:** Flask web framework patterns: app package layout, routes, blueprints, templates, and error handling
Technology reference for Flask web applications. This file does not change between projects.
Prerequisites: `stack/common.md`, `stack/python.md`
---
## 1. Directory Layout
**Rule**: Python application code lives in `app/`. The project root contains only `run.py` (entry point), `config.py`, and infrastructure files.
```
project/
run.py # Entry point — imports and runs create_app()
config.py # Config classes (Dev, Test, Prod)
pytest.ini # Pytest configuration
requirements.txt
app/
__init__.py # create_app() factory
routes.py # Blueprint route handlers
ops.py # Business logic (no Flask imports needed)
errors.py # Error handlers (optional — can live in __init__.py)
db.py # Database init and get_db()
startup.py # Startup validation
templates/
base.html
_nav.html
types/ # Type-specific partials
static/
css/
js/
tests/
conftest.py
test_smoke.py
test_routes.py
test_db.py
bin/
common.sh
start.sh
stop.sh
test.sh
```
Rules:
- `run.py` is the only Python file executed directly — all other code is imported
- `app/` is a Python package (`__init__.py` contains `create_app()`)
- `config.py` stays in root because `run.py` and tests both import it
- `ops.py` holds business logic — no Flask imports, pure Python
- `db.py` holds database initialization and `get_db()` helper
- Additional modules (scanner, scheduler, etc.) go in `app/` — never in root
- Templates and static files live under `app/` (Flask finds them automatically)
**Why**: Flat root directories mix infrastructure with application code. The `app/` package keeps all application Python in one importable namespace.
---
## 2. Application Factory
**Rule**: Use a `create_app()` factory function in `app/__init__.py`. Initialize extensions and register blueprints inside it. Provide `run.py` as the entry point.
```python
# run.py
import os
from dotenv import load_dotenv
load_dotenv()
from app import create_app
application = create_app()
if __name__ == '__main__':
port = int(os.environ.get('APP_PORT', 5001))
application.run(port=port, debug=True)
```
```python
# app/__init__.py
from flask import Flask
def create_app(config=None):
app = Flask(__name__)
app.config.from_object(config or 'config.DevConfig')
# Initialize database
from app.db import init_db
with app.app_context():
init_db()
# Register routes
from app.routes import bp
app.register_blueprint(bp)
# Register error handlers
from app.errors import register_error_handlers
register_error_handlers(app)
# Startup validation
from app.startup import validate_startup
validate_startup(app)
return app
```
Rules:
- `load_dotenv()` runs before `create_app()`, so `python run.py` and `uv run run.py` both start on a clean shell with no variable exported by hand. Neither Flask nor uv loads `.env` for you.
- Flask needs `SECRET_KEY` for sessions and flash messages. It is required configuration, read from the environment, never hardcoded and never defaulted in production code.
- `create_app(overrides)` must not skip the real configuration load as a whole. A factory that reads `Config.load()` only when it receives no overrides is exercised solely by its test seam: the suite passes with hardcoded test values while the operator's `python run.py` is the only code path that ever touches the environment.
- `run.py` is importable without starting a server: the module level builds the application, and only the `if __name__ == '__main__':` guard calls `application.run()`.
- The development server and production WSGI workers are threaded. Anything constructed once inside `create_app()` and then used from a view is shared across request threads and must be thread-safe. A `sqlite3.Connection` is the common trap: it is bound to the thread that opened it, so one opened during `create_app()` passes every single-threaded test and raises `ProgrammingError` on the first request. See `stack/persistence.md` §1 for the required per-thread connection ownership.
**Why**: Factory pattern enables creating multiple app instances with different configs — essential for testing. Deferred imports prevent circular dependencies. `run.py` as entry point keeps `app/` a clean package. Loading `.env` at the entry point is what makes a delivered application start with no setup step.
---
## 3. Blueprints and Route Organization
**Rule**: Keep route handlers thin. Extract business logic into separate modules. Group related routes with Blueprints.
```python
# app/routes.py
from flask import Blueprint, render_template, request, jsonify
from app import ops
bp = Blueprint('main', __name__)
@bp.route('/projects')
def projects_list():
projects = ops.get_all_projects()
return render_template('projects.html', projects=projects)
@bp.route('/api/project/<int:project_id>/start', methods=['POST'])
def start_project(project_id):
result = ops.start_service(project_id) # Logic in ops.py, not here
return jsonify(result)
```
```python
# app/ops.py — business logic, no Flask imports needed
from app.db import get_db
def get_all_projects():
db = get_db()
return db.execute('SELECT * FROM projects ORDER BY title').fetchall()
def start_service(project_id):
...
return {'status': 'started', 'pid': pid}
```
Rules:
- Route handlers do: parse request, call business logic, return response
- Route handlers don't: contain SQL, file I/O, subprocess calls, or complex logic
- One Blueprint per feature area for larger apps
- API routes return JSON; page routes return rendered templates
**Why**: Thin routes are testable and readable. Business logic in separate modules can be reused without Flask context.
---
## 4. Error Handling
**Rule**: Register Flask error handlers for 404/500. Return JSON for API routes, HTML for page routes. Never expose stack traces.
```python
# app/errors.py
from flask import render_template, jsonify, request
def register_error_handlers(app):
@app.errorhandler(404)
def not_found(e):
if request.path.startswith('/api/'):
return jsonify({'error': 'Not found'}), 404
return render_template('404.html'), 404
@app.errorhandler(500)
def server_error(e):
app.logger.error('Internal error: %s', e, exc_info=True)
if request.path.startswith('/api/'):
return jsonify({'error': 'Internal server error'}), 500
return render_template('500.html'), 500
@app.errorhandler(Exception)
def unhandled_exception(e):
app.logger.error('Unhandled exception: %s', e, exc_info=True)
return render_template('500.html'), 500
```
**Why**: Dual response format (JSON/HTML) keeps API clients and browsers happy.
---
## 5. Templates (Jinja2)
**Rule**: Use template inheritance with `base.html`. Prefix partials with `_`. Keep logic out of templates.
```html
<!-- app/templates/base.html -->
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}App{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
{% block head %}{% endblock %}
</head>
<body>
{% include '_nav.html' %}
<main class="container">
{% block content %}{% endblock %}
</main>
{% block scripts %}{% endblock %}
</body>
</html>
```
```html
<!-- app/templates/projects.html -->
{% extends 'base.html' %}
{% block title %}Projects{% endblock %}
{% block content %}
<h1>Projects</h1>
{% for p in projects %}
{% include 'types/_project_row.html' %}
{% endfor %}
{% endblock %}
```
Rules:
- All pages extend `base.html`
- Partials prefixed with `_` (e.g., `_nav.html`, `_project_row.html`)
- Type-specific partials in `templates/types/`
- No Python logic in templates — pass ready-to-render data from routes
- Use `url_for('static', filename=...)` for all static file references
- Auto-escaping is enabled by default — don't disable it
**Why**: Template inheritance eliminates duplication. Partial naming conventions make includes discoverable.
---
## 6. Context Processors
**Rule**: Use context processors to inject global data into all templates.
```python
@app.context_processor
def inject_globals():
return {
'app_name': app.config.get('APP_NAME', 'My App'),
'running_count': ops.get_running_count(),
}
```
**Why**: Avoids passing the same variables to every `render_template()` call.
---
## 7. HTMX Integration
**Rule**: Use HTMX for dynamic UI updates. Return HTML fragments from API endpoints. Use `HX-Trigger` headers for cross-component updates.
```python
@bp.route('/api/project/<int:project_id>/toggle', methods=['POST'])
def toggle_status(project_id):
new_status = ops.toggle_status(project_id)
project = ops.get_project(project_id)
return render_template('types/_project_row.html', project=project)
```
```html
<button hx-post="/api/project/{{ p.id }}/toggle"
hx-swap="outerHTML"
hx-target="closest tr">
Toggle
</button>
```
### OOB (Out-of-Band) Swaps
```python
html = render_template('types/_project_row.html', project=project)
html += render_template('_nav_badge.html', count=running_count)
response = make_response(html)
response.headers['HX-Trigger'] = 'projectUpdated'
return response
```
```html
<span id="running-badge" hx-swap-oob="true">{{ count }}</span>
```
**Why**: HTMX eliminates JavaScript for common interactions. HTML fragments are simpler than JSON APIs for server-rendered apps.
---
## 8. Testing with Flask Test Client
**Rule**: Test through the Flask test client. Use fixtures for app, client, and database.
```python
# tests/conftest.py
import pytest
from app import create_app
from config import TestConfig
@pytest.fixture
def app():
app = create_app(TestConfig)
yield app
@pytest.fixture
def client(app):
return app.test_client()
@pytest.fixture
def db(app):
from app.db import get_db, init_db
with app.app_context():
init_db()
yield get_db()
```
```python
# tests/test_routes.py
def test_projects_page(client):
response = client.get('/projects')
assert response.status_code == 200
assert b'Projects' in response.data
def test_api_returns_json(client):
response = client.get('/api/projects')
assert response.content_type == 'application/json'
def test_htmx_endpoint(client):
response = client.post('/api/project/1/toggle',
headers={'HX-Request': 'true'})
assert response.status_code == 200
```
**Why**: Test client exercises the full stack without a running server.
---
## 9. Security
**Rule**: Set secure headers. Validate all input. Debug mode off in production.
```python
@app.after_request
def set_headers(response):
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
return response
```
Flask security checklist:
- Jinja2 auto-escaping enabled (default — don't disable)
- `SECRET_KEY` set from environment variable in production
- `secure_filename()` from werkzeug for file uploads
- Input validation on all form data (length, type, allowed values)
- Debug mode disabled in production (`FLASK_DEBUG=0`)
---
## 10. Health Check
**Rule**: Expose a `/health` endpoint that verifies database connectivity.
```python
@bp.route('/health')
def health():
try:
db = get_db()
db.execute('SELECT 1')
return jsonify({'status': 'ok'}), 200
except Exception as e:
return jsonify({'status': 'error', 'detail': str(e)}), 500
```
---
## 11. Debug Mode and Reloading
**Rule**: Use Flask's debug mode in development only. Understand the reloader behavior.
```bash
# Development
FLASK_DEBUG=1 flask run --port 5001
# Production
gunicorn "app:create_app()"
```
Key behaviors in debug mode:
- **Auto-reloader**: Restarts server on Python file changes
- **Double startup**: `WERKZEUG_RUN_MAIN` check needed to avoid running startup code twice
- **Interactive debugger**: Shows in browser on errors (never expose in prod)
```python
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true' or not app.debug:
run_scanner()
```
---
## Standard bin/ Scripts for Flask
```bash
# bin/start.sh
#!/bin/bash
# CommandCenter Operation
# Name: Service Start
# Type: daemon
# Port: 5001
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/.."
source venv/bin/activate
FLASK_DEBUG=1 flask run --port 5001 2>&1
```
```bash
# bin/stop.sh
#!/bin/bash
# CommandCenter Operation
# Name: Service Stop
# Type: batch
pkill -f "flask run --port 5001" || echo "No Flask process found"
```
```bash
# bin/test.sh
#!/bin/bash
# CommandCenter Operation
# Name: Run Tests
# Type: batch
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/.."
source venv/bin/activate
python -m pytest tests/ -v 2>&1
```
---
## 12. Two-Tier Navigation
Financial-professional nav with four injectable zones. Content (link text, URLs) comes from the context processor fed by the project's `UI-GENERAL.md` — nothing is hardcoded in the template.
**Zone map:**
| Zone | Tier | Position | Variable |
|------|------|----------|----------|
| Utility links | 1 — top strip, desktop only | Right-justified | `util_links` list |
| Logo | 2 — primary nav | Far left | `app_name` (styled text) |
| Nav links | 2 — primary nav | Center-left, hamburger on mobile | `nav_items` list |
| CTA button | 2 — primary nav | Far right (optional) | `cta_label`, `cta_url` |
**`app/templates/_nav.html`:**
```html
<!-- Zone 1: utility bar — right-justified, hidden on mobile -->
{% if util_links %}
<div class="navbar-util py-1 d-none d-lg-block">
<div class="container-fluid">
<div class="d-flex justify-content-end gap-4">
{% for link in util_links %}
<a href="{{ link.url }}">{{ link.label }}</a>
{% endfor %}
</div>
</div>
</div>
{% endif %}
<!-- Zones 2-4: primary nav -->
<nav class="navbar navbar-expand-lg navbar-schwab">
<div class="container-fluid">
<!-- Zone 2: logo — app_name as styled text -->
<a class="navbar-brand sch-logo" href="/">{{ app_name }}</a>
<!-- Mobile hamburger -->
<button class="navbar-toggler border-0" type="button"
data-bs-toggle="collapse" data-bs-target="#primaryNav"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon" style="filter: invert(1)"></span>
</button>
<div class="collapse navbar-collapse" id="primaryNav">
<!-- Zone 3: nav links -->
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
{% for item in nav_items %}
<li class="nav-item">
<a class="nav-link {% if request.path == item.url %}active{% endif %}"
href="{{ item.url }}">{{ item.label }}</a>
</li>
{% endfor %}
</ul>
<!-- Zone 4: CTA button (omitted if cta_label is None) -->
{% if cta_label %}
<a href="{{ cta_url }}" class="btn btn-cta ms-3">{{ cta_label }}</a>
{% endif %}
</div>
</div>
</nav>
```
**Context processor** — injects all zones; values populated from project spec:
```python
# app/__init__.py or app/routes.py
@app.context_processor
def inject_nav():
return {
'util_links': [], # [{'label': str, 'url': str}, ...] — per UI-GENERAL.md
'nav_items': [], # [{'label': str, 'url': str}, ...] — per UI-GENERAL.md
'cta_label': None, # str — primary CTA text, or None to hide button
'cta_url': '/', # str — CTA destination
}
```
**`base.html` — include the nav and load theme font:**
```html
<head>
...
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Raleway:wght@300;400;700&display=swap" rel="stylesheet">
</head>
<body>
{% include '_nav.html' %}
<main class="container-fluid mt-4">
{% block content %}{% endblock %}
</main>
</body>
```
---
## Summary Checklist
- [ ] Directory layout: `app/` package, `run.py` entry point, `config.py` in root
- [ ] Application factory with `create_app()` in `app/__init__.py`
- [ ] Blueprints with thin route handlers
- [ ] Error handlers for 404/500 (JSON + HTML)
- [ ] Template inheritance from `base.html`, partials prefixed with `_`
- [ ] Context processors for global template data
- [ ] HTMX for dynamic UI (HTML fragments, OOB swaps)
- [ ] Test client fixtures in conftest.py
- [ ] Secure headers and input validation
- [ ] `/health` endpoint
- [ ] Debug mode only in dev, `WERKZEUG_RUN_MAIN` guard
- [ ] Standard `bin/` scripts: start.sh, stop.sh, test.sh
</pblock>
<pblock filename="sqlite.md" role="stack" path="/mnt/c/Users/barlo/projects/drydock/Rigging/stack/sqlite.md">
# SQLite Best Practices
**Version:** 20260603 V1
**Category:** Persistence
**Description:** SQLite database patterns: schema, queries, migrations, and connection management
Technology reference for SQLite in Python applications. This file does not change between projects.
Prerequisites: `stack/python.md`, `stack/persistence.md`
**Encapsulation**: the connection, helpers, and migrations below are the *internals* of the
`Database`/table classes defined in `stack/persistence.md`. Application code calls
`db.<table>.method()` — it never calls `get_db()`, `query()`, or `execute()` directly.
---
## Connection Setup
**Rule**: Always enable WAL mode, foreign keys, and use Row factory.
`get_db()` is the *configuration* helper. It opens and configures one connection; it does not
decide who holds it. `Database.connect()` in `stack/persistence.md` §1 calls it once per thread.
```python
import sqlite3
import os
def get_db(db_path):
os.makedirs(os.path.dirname(db_path), exist_ok=True)
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
conn.execute('PRAGMA journal_mode=WAL')
conn.execute('PRAGMA foreign_keys=ON')
return conn
```
### PRAGMAs
| PRAGMA | Value | Why |
|--------|-------|-----|
| `journal_mode=WAL` | Write-Ahead Logging | Allows concurrent reads during writes |
| `foreign_keys=ON` | Enforce FK constraints | SQLite disables FK enforcement by default |
### Threads
**Rule**: The result of `get_db()` belongs to the calling thread. Never cache it on an object
that other threads reach.
This rule is not about writing concurrent code. Application code stays sequential. A web server
hands each request to a worker thread on its own, so an application acquires threads whether or
not it ever uses them, and the only requirement is that each one gets its own connection.
A `sqlite3.Connection` is bound to its creating thread. Using it elsewhere raises:
```
sqlite3.ProgrammingError: SQLite objects created in a thread can only be used in that same thread.
```
This is the default failure mode for any threaded server. The application is constructed on the
main thread; each request is handled on a worker thread. A connection opened during construction
and cached on a module-level or instance attribute works in every single-threaded unit test and
fails on the first HTTP request. Hold one connection per thread — see `stack/persistence.md` §1.
WAL is what makes per-thread connections worth having: each thread's connection reads
concurrently while another writes.
`check_same_thread=False` is **not** the fix. It disables the safety check without making the
connection safe, leaving cursor and transaction state shared across threads. If a single shared
connection is genuinely required, it needs an explicit lock around every statement — which
serializes reads and discards the benefit of WAL.
Startup work — `initialize()`, `_run_migrations()`, seeding — runs on whichever thread starts the
application, against that thread's own connection. That is correct and needs no special handling;
it is simply not the connection request handlers use.
Long-lived worker pools accumulate one connection per thread. That is acceptable at the
single-server scale SQLite is recommended for below.
---
## Schema Design
**Rule**: Use `TEXT` for dates (ISO 8601), `INTEGER` for booleans, and a JSON `TEXT` column for extensible fields.
```sql
CREATE TABLE IF NOT EXISTS items (
id INTEGER PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
status TEXT DEFAULT 'active',
extra TEXT DEFAULT '{}', -- JSON for extensible fields
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now'))
);
```
### JSON Column Pattern
```python
import json
def get_extra(row):
return json.loads(row['extra'] or '{}')
def set_extra(db, item_id, key, value):
row = db.execute('SELECT extra FROM items WHERE id = ?', (item_id,)).fetchone()
extra = json.loads(row['extra'] or '{}')
extra[key] = value
db.execute('UPDATE items SET extra = ? WHERE id = ?', (json.dumps(extra), item_id))
db.commit()
```
**Why**: JSON columns let you add fields without migrations.
---
## Queries
**Rule**: Always use parameterized queries. Never interpolate user input into SQL.
```python
# CORRECT — parameterized
db.execute('SELECT * FROM items WHERE id = ?', (item_id,))
db.execute('INSERT INTO items (name, status) VALUES (?, ?)', (name, status))
# NEVER — string interpolation
db.execute(f"SELECT * FROM items WHERE id = {item_id}") # SQL INJECTION
```
### Helper Patterns
```python
def row_to_dict(row):
"""Convert sqlite3.Row to dict, parsing JSON columns."""
if row is None:
return None
d = dict(row)
if 'extra' in d:
d['extra'] = json.loads(d['extra'] or '{}')
return d
def query(db, sql, params=(), one=False):
rows = db.execute(sql, params).fetchall()
if one:
return row_to_dict(rows[0]) if rows else None
return [row_to_dict(r) for r in rows]
def execute(db, sql, params=()):
cursor = db.execute(sql, params)
db.commit()
return cursor.lastrowid
```
---
## Migrations
**Rule**: Run migrations at startup using `PRAGMA table_info()` to detect missing columns.
```python
def _run_migrations(db):
"""Add columns that don't exist yet. Runs on every startup."""
columns = {r['name'] for r in db.execute('PRAGMA table_info(items)').fetchall()}
migrations = [
('new_field', 'ALTER TABLE items ADD COLUMN new_field TEXT DEFAULT ""'),
('priority', 'ALTER TABLE items ADD COLUMN priority INTEGER DEFAULT 0'),
]
for col_name, sql in migrations:
if col_name not in columns:
db.execute(sql)
db.commit()
```
### New Table Detection
```python
def _table_exists(db, table_name):
row = db.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
(table_name,)
).fetchone()
return row is not None
```
**Why**: Startup migrations avoid manual schema management. `PRAGMA table_info` is idempotent.
---
## Limitations
| Scenario | SQLite OK? | Alternative |
|----------|-----------|-------------|
| Single-server web app | Yes | — |
| Local tool / CLI | Yes | — |
| Multiple concurrent writers | No | PostgreSQL |
| Multi-server deployment | No | PostgreSQL |
| Dataset > 1GB | Maybe | PostgreSQL |
| Full-text search (heavy) | Maybe | PostgreSQL + pg_trgm |
---
## Backup
**Rule**: Use file copy with WAL checkpoint first.
```python
import shutil
def backup_database(db_path, backup_dir='data/backups'):
os.makedirs(backup_dir, exist_ok=True)
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_path = os.path.join(backup_dir, f'backup_{timestamp}.db')
conn = sqlite3.connect(db_path)
conn.execute('PRAGMA wal_checkpoint(TRUNCATE)')
conn.close()
shutil.copy2(db_path, backup_path)
return backup_path
```
**Why**: WAL checkpoint flushes pending writes before copying.
</pblock>
IMPLEMENTS - Authoritative Step Specifications
<pblock label="Implementation recency anchor" kind="section"> The files in this section are the load-bearing specifications for this build block. Build these files exactly. Treat earlier sections as constraints and context.
</pblock>
<pblock filename="ARCHITECTURE.md" role="implements" path="/mnt/c/Users/barlo/projects/drydock/uat/ReadingList/runs/20260815.171255/workspace/targets/ReadingList/blueprint/ARCHITECTURE.md" guidance="Foundational Architecture Specification">
# ARCHITECTURE: ReadingList
| Field | Value |
|-------------|-------|
| Version | 20260815 V1 |
| Description | Defines the Flask application structure and module boundaries for the ReadingList web application. |
| Depends On | — |
| Provides | application_factory, web_entrypoint |
| Consumes | — |
## Intent
ReadingList is a small Flask web application for maintaining an ordered personal list of books to read. The application uses one reader-facing workflow: submit a book, review the ordered list, and remove a book.
## Modules and Boundaries
| Module | Responsibility |
|---|---|
| `app/__init__.py` | Application factory and configuration initialization. |
| `app/routes.py` | HTTP route registration and request/response orchestration. |
| `app/persistence.py` | SQLite connection management and the typed book-store boundary. |
| `app/templates/` | Reader-facing HTML templates. |
| `app/static/` | Shared CSS presentation. |
| `tests/` | Automated behavior tests. |
| `bin/test.sh` | POSIX-compatible complete-suite launcher. |
The application factory creates an isolated Flask application, configures the database location, initializes the persistence boundary, and registers routes. Route handlers do not execute raw SQL. Persistence code does not render templates or make HTTP decisions.
## Technical Decisions
- Use a Flask application factory so tests can create isolated application instances.
- Use SQLite for local persistence.
- Preserve insertion order with an integer primary key and an ordered query.
- Keep the initial workflow on one screen at `/`.
- Use redirects after successful form mutations so the list is read again from persistence.
## Technology Stack
| Technology | Use |
|---|---|
| Python | Application and test implementation. |
| Flask | Web application, routing, templates, and test client. |
| SQLite | Local persistent storage for books. |
| pytest | Automated behavior test runner. |
| HTML/CSS | Reader-facing interface and shared presentation. |
## Module Ownership
| Boundary | Owning module | Allowed low-level access |
|---|---|---|
| Application configuration | Application factory | Flask configuration APIs and environment values. |
| Persistence | `app.persistence` | SQLite connections and SQL statements. |
| HTTP | `app.routes` | Flask request, response, redirect, and rendering APIs. |
| Presentation | Templates and static assets | HTML, template variables, and CSS only. |
| Verification | `tests/` and `bin/test.sh` | Test and process execution APIs. |
## Programmatic Acceptance
=== AC architecture-factory ===
Intent: The application factory creates a runnable Flask application with an HTTP test client.
from app import create_app
application = create_app({"TESTING": True, "DATABASE": ":memory:"})
client = application.test_client()
response = client.get("/")
assert response.status_code == 200
=== END AC architecture-factory ===
=== AC architecture-isolation ===
Intent: Separate application instances can be created independently for isolated execution.
from app import create_app
first = create_app({"TESTING": True, "DATABASE": ":memory:"})
second = create_app({"TESTING": True, "DATABASE": ":memory:"})
assert first is not second
assert first.test_client().get("/").status_code == 200
assert second.test_client().get("/").status_code == 200
=== END AC architecture-isolation ===
=== AC architecture-entrypoint ===
Intent: The web entrypoint exposes the application factory without requiring a development server to start during import.
import importlib
module = importlib.import_module("app")
assert callable(module.create_app)
=== END AC architecture-entrypoint ===
## User Acceptance
- None.
## Guardrails
- Route handlers must not access SQLite directly.
- Persistence code must not reorder books.
- The application must remain runnable as a web application.
</pblock>
<pblock label="Build instructions" kind="instructions">
Build instructions for this block
Establish the Flask application architecture and module boundaries. (architecture)
Establish the Flask application structure, application factory, route registration boundary, configuration boundary, and ownership rules for persistence and web concerns. Keep the design suitable for a small runnable web application and preserve insertion order for books.
</pblock>
<pblock label="Reusable compact request" kind="section">
Reusable compacts
The Blueprint sources below are consumed as context by later Manifest blocks. In this same response, extract their consumer-facing contract surface. Preserve interfaces, schemas, constraints, configuration, and cross-file obligations; drop implementation narrative and repetition. Do not write these files yourself.
Before the required RESULT block, emit one optional payload per source exactly as: <reusable-compact filename="SOURCE.md"> compact content </reusable-compact>
Emit no payload when a source has no useful technical surface. These payloads are advisory and do not change the required build result or file-change report.
Sources eligible for reusable compaction:
- ARCHITECTURE.md
</pblock>
Agent Task
You are a Drydock build agent implementing exactly one build step of a larger plan. The build job block below names the target, the build working directory, and the step. Everything you need is stacked into this prompt under role headings:
compass— the Target's COMPASS.md orientation.implements— the Typed Specification files this step builds. These are
authoritative; implement them exactly.
context— read-only support specifications. Do not reimplement them.stack— enterprise stack and technology rules. Honor them.rules— governance and branding rules. Honor them.
Operating contract:
- Follow the write authorization and protected paths in the stacked
COMPASS.mdexactly.
That persisted guardrail is the sole authority for paths this build may modify.
- Start by inspecting the build working directory. Preserve existing application
files unless this step's specifications require a change. Its sources/ subdirectory holds staged build assets — imported test corpora, conformance harnesses, and fixtures — placed there for you. They are read-only inputs: run them, import them, and write code against them, but never create, rewrite, trim, regenerate, or substitute one, even to make a check pass. A step that modifies a staged asset fails and the asset is restored. If an asset you expect is absent, report that; do not author a replacement.
- Implement only this step. Use
context,stack, andrulesas constraints,
not as additional work to perform.
- Follow the stack and rules for languages, structure, naming, and branding.
- The programmatic acceptance assertions in the
implementsspecifications are
this step's Definition of Done — human-owned, declared before the build, and fixed. Build the story and, in this same step, write the deterministic tests that prove each declared assertion, as a TDD master would; add finer tests for coverage. Every test you write follows the same rule the acceptance assertions do: act on the system, read the state back, compare to expected. The oracle is a return value, parsed JSON, a status code, a stored row, file contents read back, or an exit status — never a substring of captured stdout or stderr, a test-runner tally, or a log line. Write tests in the project's own language using that language's libraries; an in-language HTTP client yields a status code and a parsed body, where curl yields text to scrape. Round-trip anything that stores state: act, then read back through the public interface. Assert declared failure signals on negative paths, never message wording. You may add tests but must never remove, soften, or weaken a declared acceptance assertion. A Suite: full conformance check gates on the entire imported test suite: the step is done only when it passes in full, never on a representative subset — reproduce the standard exactly rather than wrapping a third-party library that approximates it. For a suite, the runner's exit status is the verdict and the whole verdict: print its captured output for diagnosis, never assert on the text of its summary. When an assertion is a static or filesystem scan (import boundary, "X never appears outside Y," grep/AST gate), honor the scope the specification states and never widen it: scan production source only, exclude .venv/, site-packages, and vendored or generated code, and do not flag test doubles or fixtures that use the guarded dependency. Run every declared acceptance assertion before returning. For a conformance suite, use its section or example filters to diagnose coherent root-cause clusters, but rerun the full declared scope before reporting the result. Treat failing examples as a work queue for fixing general behavior; never add example-specific exceptions.
- Grow the project's own test suite as you write the code, and treat it as the project's
real coverage. The acceptance assertions in implements are gates: few, fixed, and written before any code existed, so every expectation in them is a prediction. The tests you write are written beside the finished code, so their expected values are observed rather than predicted — which is why exhaustive coverage belongs here and not there. Extend the suite in the project's established location and runner, keep it runnable by the project's declared test command, and leave it green when you return. Cover, at minimum: every public entry point and every verb it declares, including declared error paths; the boundaries — empty, exactly one, many, absent optional fields, declared maxima; declared idempotence, applied twice; one behavior per test, named for the behavior; and isolation — each test arranges its own data, with a fresh store or explicit teardown, so a run leaves no residue behind in the build directory. Where a staged authoritative suite already covers a surface, that suite is the coverage: run it, and do not restate its cases. Report the suite's pass/fail counts in your SUMMARY so a reader can see coverage moving across steps.
- Treat
User Acceptanceentries as review evidence requirements. Implement
the supporting behavior, but do not claim to have performed human judgment.
- The
implementssection is authoritative and intentionally stacked late in
the prompt as the recency anchor. Build that WHAT exactly; do not substitute generic framework defaults.
- Before adding or installing Python dependencies, verify each package name
against the declared registry. Do not invent package names. If a needed package cannot be verified or appears newly published, fail explicitly instead of installing it.
- Use the stack's required package manager workflow for dependency changes.
When the stack requires uv, update manifests through uv conventions rather than bare pip install.
- Do not claim success unless you actually created or modified project files in
the build working directory. If you cannot write files or cannot complete the step, report failure explicitly.
- Do not run
git add,git commit, create branches, create tags, rewrite
history, or otherwise mutate Git history. Drydock owns the final build directory commit after you return.
- End your response with this exact closing structure:
RESULT: SUCCESS | FAILED
FILES CHANGED:
- relative/path
SUMMARY:
<brief reviewable summary>
BLOCKERS:
- <only if any>
Before RESULT, you may emit one optional JSON payload when implementation required a bounded choice not already settled by the owning specification. This records what you did; it does not ask permission, create a questionnaire, or excuse incomplete work:
<blueprint-decisions>
[{"spec":"FEATURE-Example.md","severity":"Material","subject":"Chosen behavior","decision":"Options A and B were available. I implemented B because ... Is that acceptable, or should this change on replan?"}]
</blueprint-decisions>
Name only a specification implemented by this build block. Use Low or Material; Build never emits a Blocking decision. Omit the payload when no implementation decision was necessary.
FILES CHANGEDmust list only files actually written in the build working
directory. If no files were written, use RESULT: FAILED.
- On
RESULT: FAILED, append two additional lines so the failure is actionable
without opening logs. FAILURE_SUMMARY is one line naming the cause; FAILURE_DETAIL states what happened, why, and what to change before a rerun. Name concrete conditions when they apply: token or context limit exceeded, could not execute commands in this environment, a required input was missing, or a specific tool or command failed.
FAILURE_SUMMARY: <one line naming the cause>
FAILURE_DETAIL: <what happened, why, and what to change before rerunning>
- When a declared acceptance criterion cannot pass no matter how the code is written,
say so with this exact token. You may not edit the criterion — it is staged and restored before grading:
AC_BROKEN: <check-id>[, <check-id>]
This is a report, not a verdict, and it stops nothing. A criterion reaches you only when its expected value is one its author could not have invented — a status code, a staged suite's exit status, a value the criterion itself supplied as input — so your claim that the criterion rather than the code is at fault is the less likely explanation, and the budget is spent as it would be for any other failure. A criterion whose expectation was hand-typed already settles DISPUTED on its own, without you naming it. Emit the token only after running the criterion and confirming the underlying command succeeded while the assertion still failed. Name the affected check ids, emit it alongside your normal RESULT line, state the reasoning in FAILURE_DETAIL, and emit it even when RESULT: SUCCESS. Do not use it for a criterion you merely failed to satisfy.
Active Commander guidance
Commander decisions
Apply these decisions as constraints; do not recreate them as questions.
analyze-display_name
- Title: Display Name
- Decision: ReadingList
- Severity: material
- Blueprint: ARCHITECTURE.md
analyze-short_description
- Title: Short Description
- Decision: A web application for maintaining an ordered list of books to read.
- Severity: material
- Blueprint: ARCHITECTURE.md