CLAUDE.md · git:20260905.cb5cd4a · 2026-09-05 · sha256 bf3efad169477b18
CLAUDE.md git:20260905.cb5cd4aA
Immutable. This exact content is served forever at /api/v1/blob/bf3efad169477b18.
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Overview
This is a research project template with a test-driven development workflow, automated PDF generation, and multi-project support. It uses a two-layer architecture separating generic infrastructure (Layer 1) from project-specific code (Layer 2), following a thin orchestrator pattern.
### How this file fits with other entry points
| File | Use when you need |
| --- | --- |
| [`START_HERE.md`](START_HERE.md) | The Step 0-5 cold-start path: prerequisites, clone, `uv sync`, pre-commit, first pipeline run, verify outputs |
| [`README.md`](README.md) | First-time setup, documentation map, contributor links |
| **This file (`CLAUDE.md`)** | Copy-paste commands, CI parity, common code patterns |
| [`AGENTS.md`](AGENTS.md) | Full pipeline semantics, validation, configuration reference, troubleshooting index |
| [`.cursorrules`](.cursorrules) | Cursor-focused agent rules (overlap with this file by design) |
| [`.github/AGENTS.md`](.github/AGENTS.md) | Exact CI job names, coverage thresholds, branch protection hints |
## Quick Reference
| Task | Command |
| --- | --- |
| Interactive menu | `./run.sh` |
| Reproducible run matrix (project × stage; preferred over the menu for repeatable subset runs) | `cp run.config.example.yaml run.config` first (the CLI errors without one), then `uv run python scripts/runner/run_matrix.py` (reads `run.config`; `--dry-run` to preview, `--fail-fast` to stop on first failure; see [`run.config.example.yaml`](run.config.example.yaml)) |
| Secure workflow via main shell (`secure` subcommand) | `./run.sh --secure-run` |
| Full pipeline | `./run.sh --pipeline` |
| Core pipeline (no LLM) | `uv run python scripts/runner/execute_pipeline.py --project {name} --core-only` |
| Incremental pipeline (opt-in stage skipping) | `uv run python scripts/runner/execute_pipeline.py --project {name} --incremental` (also `python -m infrastructure.orchestration pipeline --project {name} --incremental`; default off) |
| Project pipeline tests | `uv run python scripts/pipeline/stage_01_test.py --project {name}` |
| Full infrastructure gate | `uv run python scripts/pipeline/stage_01_test.py --infra-only --infra-scope full` |
| Single test | `uv run pytest path/to/test.py::test_function -v` |
| Install deps | `uv sync` (root `default-groups` includes `public-exemplars`, covering all deterministic public-project dependencies; live research/publishing groups remain opt-in) |
| Editor Python | `.venv/bin/python` after `uv sync` (see `.vscode/settings.json`) |
| Public CI source paths | `uv run python -m infrastructure.project.public_scope source-paths` |
| Ruff (CI scope) | `uv run python -m infrastructure.project.public_scope lint-paths \| xargs uv run ruff check --fix && uv run python -m infrastructure.project.public_scope lint-paths \| xargs uv run ruff format` |
| Mypy (CI scope) | `uv run python -m infrastructure.project.public_scope source-paths \| xargs uv run mypy` |
| Bandit (CI / security job) | `uv run bandit -c bandit.yaml -r -ll infrastructure/ scripts/ projects/` (exclusions in `bandit.yaml` → `exclude_dirs`) |
| Pre-commit (lint stage) | `pre-commit run --all-files` |
| Pre-push hooks | `pre-commit run --hook-stage pre-push --all-files` |
| Local CI reproduction (act + fallback) | `./scripts/shell/ci_local.sh` (added 2026-05-20; see [`docs/maintenance/ci-local.md`](docs/maintenance/ci-local.md)) |
| Executable bundle (opt-in stage; see pipeline table) | `uv run python scripts/runner/bundle_executable.py --project {name}` |
| Archive publication dry-run (opt-in stage; see pipeline table) | `uv run python scripts/runner/archive_publication.py --project {name}` |
| Archive publication real deposit | `uv run python scripts/runner/archive_publication.py --project {name} --providers zenodo software_heritage ipfs_pinata --commit` (requires credentials — see [`docs/maintenance/archival-targets.md`](docs/maintenance/archival-targets.md)) |
| Publication runbook (standalone GitHub + real Zenodo DOI + optional mirrors) | [`docs/guides/publication-runbook.md`](docs/guides/publication-runbook.md) |
| Unified project release (GitHub + Zenodo + DOI) | `uv run python scripts/publish/publish_project_release.py --project {name} --tag v1.0.0 --repo owner/repo` (opt-in; see [`docs/guides/publication-runbook.md`](docs/guides/publication-runbook.md)) |
| Reproduction bundle (single / all public exemplars) | `uv run python scripts/runner/repro_bundle.py build {name}` or `... build --all-public --out output/repro_bundles` (verify with `... verify <manifest>`) |
| Regression tests (claim-binding tier) | `uv run pytest tests/regression/ -v` (claim-binding tier plus a public-roster pin; collected totals in [`docs/_generated/COUNTS.md`](docs/_generated/COUNTS.md), rationale in [`docs/maintenance/regression-testing.md`](docs/maintenance/regression-testing.md)) |
| Repo-wide doc linter | `uv run python scripts/audit/lint_docs.py` |
| Exemplar drift checker | `uv run python scripts/audit/check_template_drift.py` (add `--strict` to fail on warnings, including hardcoded doc counts) |
| Re-measure exemplar coverage (slow; runs every suite) | `uv run python scripts/docgen/counts.py --verify-coverage` (add `--write` to rewrite the recorded percentages from the measurement, then `--refresh-coverage-provenance --write`) |
| Module line count gate | `uv run python scripts/gates/module_line_count_check.py` |
| CodeGraph local commands | `uv run python scripts/maintenance/codegraph_local.py commands .` (optional; see [`docs/guides/codegraph-local.md`](docs/guides/codegraph-local.md)) |
| LEANN local semantic retrieval | Optional user-level companion only; see [`docs/guides/leann-local.md`](docs/guides/leann-local.md) |
| Unified health CLI | `uv run python -m infrastructure.core.health` (optional `--gates=module-line-count`) |
| Release-readiness dashboard (no network) | `uv run python -m infrastructure.reporting.release_readiness --out output/release_readiness.md` (add `--format html`; aggregates version/coverage/pipeline/docs-lint/evidence-graph from local artifacts only) |
| Opt-in security scan | `uv run python scripts/gates/security_scan.py` (not default pipeline/CI; missing tools report `skipped`, not clean) |
| Deep research dispatch (opt-in, **PAID, separate from any subscription** — ≈$2/report OpenAI, ≈$3–7 typical Gemini (up to ~$15 on a full manuscript)) | `uv sync --group deep-research` (installs SDKs + `python-dotenv`), keys in `.env`, then `uv run python -m infrastructure.search.deep_research providers\|submit\|poll\|run-project` — full manuscripts are packaged in full; cost model + multi-project loop recipe in [`infrastructure/search/deep_research/README.md`](infrastructure/search/deep_research/README.md); never default pipeline/CI |
### CI mirror (GitHub Actions)
Workflow definitions: [`.github/workflows/ci.yml`](.github/workflows/ci.yml). Job names, matrix (Ubuntu × Python 3.10–3.14 plus macOS × 3.14), coverage floors (infra 60%, project 90%), and local reproduction commands: [`.github/AGENTS.md`](.github/AGENTS.md).
## Common Commands
### Pipeline Execution
```bash
# Interactive menu (recommended)
./run.sh
# Secure orchestration (same Python CLI as ./run.sh; forwards to `secure` subcommand)
./run.sh --secure-run
# Dedicated secure shell: ensures `uv sync --group steganography`, then `python -m infrastructure.orchestration secure`
# Pipeline phase requires `--project`; omit `--project` only with `--steganography-only` (all discovered projects).
./secure_run.sh --project {project_name}
./secure_run.sh --project {project_name} --core-only
./secure_run.sh --steganography-only --project {project_name}
./secure_run.sh --steganography-only
# Full pipeline default path (core+LLM; see STAGE_SUMMARY at the end of this file)
./run.sh --pipeline
# Core pipeline only (LLM and opt-in stages excluded)
uv run python scripts/runner/execute_pipeline.py --project {project_name} --core-only
# Resume from checkpoint
./run.sh --pipeline --resume
# Deterministic steganography timestamps (--deterministic is parsed by the Python `secure` subcommand, which sets STEGANOGRAPHY_DETERMINISTIC=1)
./secure_run.sh --deterministic --project {project_name}
```
### Testing
Never run more than one exemplar pytest tree in the same process: every exemplar's
`tests/` and `src/` packages share the same top-level names, so co-running two trees
silently reuses whichever tree imported a module first. Run each exemplar in its own
invocation (CI and the public readiness gate already do).
```bash
# Run all tests (infrastructure + project)
uv run python scripts/pipeline/stage_01_test.py --project {project_name}
# Focused project/infrastructure runs are serial unless -n is supplied. The
# quick all-public project matrix is bounded-parallel by default (at most four
# outer workers); use --project-workers serial for the diagnostic oracle.
# -n auto or a fixed -n value controls inner pytest-xdist. Do not combine it
# with project workers >1. TEMPLATE_PROJECT_WORKERS overrides the bounded auto
# cap; PYTEST_XDIST_WORKERS controls inner xdist when no -n is supplied.
uv run python scripts/pipeline/stage_01_test.py \
--project-only --all-projects --public-projects --profile quick
# Explicit serial oracle for the same public selection:
uv run python scripts/pipeline/stage_01_test.py \
--project-only --all-projects --public-projects --profile quick \
--project-workers serial
# Opt into inner-project parallelism for a focused run:
uv run python scripts/pipeline/stage_01_test.py --project {project_name} -n auto
# Infrastructure tests only (60% coverage minimum; safe local coverage lane)
COVERAGE_FILE=.coverage.infra uv run pytest tests/infra_tests/ \
-n 2 --dist loadscope --benchmark-disable \
--cov=infrastructure --cov-report=term-missing --cov-fail-under=60 \
--durations=10 \
-m "not requires_ollama and not requires_docker and not network and not slow and not bench and not benchmark and not performance" \
--timeout=120
# Uncached serial diagnostic oracle (same selection, with the xdist flags removed).
COVERAGE_FILE=.coverage.infra uv run pytest tests/infra_tests/ \
--cov=infrastructure --cov-report=term-missing --cov-fail-under=60 \
--durations=10 \
-m "not requires_ollama and not requires_docker and not network and not slow and not bench and not benchmark and not performance" \
--timeout=120
# Project tests only (90% coverage minimum)
uv run pytest projects/{project_name}/tests/ --cov=projects/{project_name}/src --cov-fail-under=90
# Run specific test file
uv run pytest tests/infra_tests/test_specific.py -v
# Run single test function
uv run pytest tests/infra_tests/test_specific.py::test_function_name -v
# Coverage files are isolated per suite (.coverage.infra, .coverage.project)
# Owned serial/parallel performance evidence (requires a clean checkout):
uv run python scripts/maintenance/benchmark_tests.py \
--target pipeline-smoke --output /tmp/template-test-performance.json
```
### Development Tools
```bash
# Install dependencies
uv sync
# Workspace management
uv run python scripts/maintenance/manage_workspace.py status
uv run python scripts/maintenance/manage_workspace.py add <package> --project <name>
# Linting and type checking (mirror CI `lint` job)
uv run python -m infrastructure.project.public_scope lint-paths | xargs uv run ruff check --fix
uv run python -m infrastructure.project.public_scope lint-paths | xargs uv run ruff format
uv run python -m infrastructure.project.public_scope source-paths | xargs uv run mypy
# Security scan (mirror CI `security` job Bandit step)
uv run bandit -c bandit.yaml -r -ll infrastructure/ scripts/ projects/
# Validate markdown
uv run python -m infrastructure.validation.cli markdown projects/{project_name}/manuscript/
# Validate PDFs
uv run python -m infrastructure.validation.cli pdf output/{project_name}/pdf/{project_name}_combined.pdf
# Local Ollama workflow
ollama serve
ollama pull gemma3:4b
uv run pytest tests/infra_tests/llm/ -m requires_ollama -v
# Optional local CodeGraph index (not a CI or publication dependency)
uv run python scripts/maintenance/codegraph_local.py commands .
codegraph init "$(pwd)" --index
codegraph files "$(pwd)" --json | uv run python scripts/maintenance/codegraph_local.py verify-scope
# Generate API documentation (positional SRC_DIR GLOSSARY_MD; no --project flag)
uv run python -m infrastructure.documentation.generate_glossary_cli projects/{project_name}/src projects/{project_name}/manuscript/98_symbols_glossary.md
# Agent SKILL.md manifest (Cursor / editors)
uv run python -m infrastructure.skills write
uv run python -m infrastructure.skills write-index
uv run python -m infrastructure.skills check
uv run python -m infrastructure.skills operations-check
# Pinned repository skills + Codex/Claude/Hermes user-runtime parity
uv run python -m infrastructure.skills runtime-status
# Intentional reversible install; existing same-name skills are backed up
uv run python -m infrastructure.skills runtime-install
# Discoverable per-template skills (Hermes / agentskills.io)
# Every exemplar under projects/templates/ ships .agents/skills/<name>/SKILL.md
# These descriptors are included in .cursor/skill_manifest.json and MCP list_skills.
# Load via Hermes: skill_view(name='template-code-project')
# List skills in a template: ls projects/templates/<name>/.agents/skills/
# Optional stdio MCP server exposing list_skills/list_operations/describe_pipeline/invoke_cli
uv run python -m infrastructure.mcp_server
```
### Multi-Project Operations
```bash
# Run all projects with full pipeline
./run.sh --all-projects --pipeline
# Run all projects with core pipeline only
uv run python scripts/runner/execute_multi_project.py --no-llm
# List available projects
uv run python -c "from infrastructure.project.discovery import discover_projects; from pathlib import Path; print([p.name for p in discover_projects(Path('.'))])"
```
**Public active projects:** Authoritative list → [`docs/_generated/active_projects.md`](docs/_generated/active_projects.md) (`infrastructure.project.public_scope`). Runtime `discover_projects()` may include local private symlinks.
**🔒 CONFIDENTIALITY INVARIANT (public repo).** The only project trees ever
git-tracked or pushed are the public canonical exemplars under
`projects/templates/` selected by
`infrastructure.project.public_scope.PUBLIC_PROJECT_NAMES`. The generated
[`docs/_generated/active_projects.md`](docs/_generated/active_projects.md) is the
authoritative roster; regenerate it after layout changes and never maintain a
second project-name allowlist in prose. Broader runtime discovery of local
private symlinks does not broaden the public tracking boundary.
`.gitignore` ignores `projects/*` and negates **only** `projects/templates/`
(the public exemplars) plus the repo-level `projects/*.md` docs. **Every other
path under `projects/` — optional `active/` hot-seat render set, the `working/`,
`ongoing/`, and `archive/` sidecar mirrors and the optional legacy `active/`
lifecycle folder — is LOCAL-ONLY and must
never be committed.** This is enforced, not conventional:
`scripts/audit/check_tracked_all.py` fails the CI `lint` job and the pre-push
`pre-push-quick` hook on any non-template tracked project (a `git add -f`
cannot slip past it). Negative control:
`test_offending_tracked_projects_flags_non_exemplar` constructs a known-wrong
tracked path and asserts the guard fails.
**The same invariant covers three sibling top-level resource-pool directories:**
`fonds/`, `rules/`, and `tools/` (each analogous to `projects/` — only their
`templates/` subfolder is git-tracked; `working/`/`archive/` are LOCAL-ONLY).
`scripts/audit/check_tracked_all.py` runs all four confidentiality checks
(`offending_tracked_projects/fonds/rules/tools` in
`infrastructure/project/git_guards.py`) in one pass — it superseded the
narrower `scripts/audit/check_tracked_projects.py` (still runnable standalone,
but no longer wired into CI or pre-commit).
Private work lives outside this public repo, usually at the sibling
`$TEMPLATE_PRIVATE_PROJECTS_ROOT`/`../projects` sidecar. The current simplified
sidecar uses `working/` and `archive/`; optional `ongoing/` (long-lived
projects with no publication target) and the legacy `active/` folder are still
supported when present. `run.sh` and
`python -m infrastructure.orchestration` auto-sync existing folders as symlinks
into matching typed subfolders under `projects/`: `working/*` →
`projects/working/*`, `ongoing/*` → `projects/ongoing/*`, `archive/*` →
`projects/archive/*`, and optional
`active/*` → `projects/active/*` (discovered + rendered alongside the
`templates/` exemplars). Inspect with
`uv run python -m infrastructure.orchestration link-projects --dry-run`;
override the root with `TEMPLATE_PRIVATE_PROJECTS_ROOT` or `.private_projects_root`;
disable one command with `TEMPLATE_SKIP_LINK_SYNC=1`. Rotating sidecar projects
usually move between `working/` and `archive/`; never hard-code their paths in
long-lived docs.
**Backburner & archived projects:** remain non-rendered unless rendered through
an explicit qualified command such as `--project working/<name>` (see
[`docs/maintenance/private-projects-repo.md`](docs/maintenance/private-projects-repo.md)).
**The same auto-sync runs for `fonds/`, `rules/`, and `tools/` on every
`run.sh` / orchestration invocation** (`infrastructure/orchestration/link_sync.py`),
each independently overridable/skippable: `TEMPLATE_FONDS_ROOT` /
`TEMPLATE_SKIP_FOND_LINK_SYNC`, `TEMPLATE_RULES_ROOT` /
`TEMPLATE_SKIP_RULE_LINK_SYNC`, `TEMPLATE_TOOLS_ROOT` /
`TEMPLATE_SKIP_TOOL_LINK_SYNC`.
## Architecture
The repository has two implementation layers:
- Generic, reusable behavior belongs in `infrastructure/`; root and project
scripts are thin orchestrators.
- Domain behavior belongs in `projects/<qualified-name>/src/`, with tests beside
the project and disposable working output under its `output/` directory.
Do not place business logic in `scripts/`. Scripts may coordinate imports, I/O,
visualization, and output reporting only. This invariant is expanded in
[`AGENTS.md`](AGENTS.md#thin-orchestrator-pattern) and
[`docs/architecture/two-layer-architecture.md`](docs/architecture/two-layer-architecture.md).
Do not hand-maintain an infrastructure-package inventory here. The live package
map and ownership rules are in [`infrastructure/AGENTS.md`](infrastructure/AGENTS.md),
while agent-operable catalogs are derived through
[`docs/architecture/capability-surfaces.md`](docs/architecture/capability-surfaces.md).
## Project Structure
Public tracked exemplars live only under `projects/templates/`. Optional
`projects/active/` links join default discovery; `working/`, `ongoing/`, and
`archive/` remain local-only and require qualified names such as
`working/<name>`. Never hard-code a rotating private project name or commit a
non-template project path.
Use [`docs/_generated/active_projects.md`](docs/_generated/active_projects.md)
for the current public/rendered roster and
[`docs/maintenance/private-projects-repo.md`](docs/maintenance/private-projects-repo.md)
for sidecar lifecycle and link-sync operations. Canonical exemplar structure is
visible in `projects/templates/template_code_project/`; do not duplicate that
derived tree diagram here.
## Pipeline Stages
[`infrastructure/core/pipeline/pipeline.yaml`](infrastructure/core/pipeline/pipeline.yaml)
is authoritative. The generated table at the end of this file is the compact
human view; refresh it with `uv run python scripts/docgen/stage_table.py` rather
than editing stage counts or names in prose. Pipeline semantics and opt-in stage
rules live in [`AGENTS.md`](AGENTS.md#rendering-pipeline).
## Testing Requirements
### No Mocks Policy
Do not introduce `MagicMock`, `mocker.patch`, `unittest.mock`, or another
mocking framework. The lexical command proves prohibited framework syntax is
absent; the inventory classifies semantic replacements. CI and pre-push enforce
a zero ceiling for dependency replacements while permitting separately
classified environment isolation.
```bash
uv run python scripts/audit/verify_no_mocks.py
uv run python scripts/audit/verify_no_mocks.py --inventory --max-dependency-replacements 0
```
**Patterns**:
- HTTP testing: Use `pytest-httpserver` for local test servers
- CLI testing: Execute subprocess commands
- PDF testing: Create PDFs with `reportlab`
- File operations: Use real temp files with `tmp_path` fixture
### Coverage Requirements
- **Infrastructure**: 60% minimum (measured baseline → [`docs/development/coverage-gaps.md`](docs/development/coverage-gaps.md))
- **Projects (per-project standalone)**: 90% minimum. A project suite below its floor fails via `--cov-fail-under` (a zero-test collection fails the gate outright rather than reporting vacuous 100%). Exemplar measured coverage → [`docs/_generated/COUNTS.md`](docs/_generated/COUNTS.md). Per-project gate: `uv run pytest projects/{name}/tests/ --cov=projects/{name}/src --cov-fail-under=90` — pytest exits non-zero when measured coverage falls below the floor. Negative control: dropping a project below its floor (or running the suite with `--no-cov`) makes that exact command exit non-zero — a green suite alone never satisfies the gate.
Coverage beneath the configured floor fails the gate, so thinning the suite cannot buy a green run.
The floor is self-demonstrating: deleting tests until measured coverage drops below it makes pytest exit non-zero through `--cov-fail-under`, which is the gate's built-in negative control.
- **Rotating-project exceptions**: a CI matrix job may pin a lower floor for a checked-out rotating project (e.g. an 89% gate for a Lean-toolchain project) when its Lean build + live external CLI + Ollama-gated paths carry CI-only surface below the 90% floor. The exception applies only while that project is checked out under `projects/`; raise back to 90% once that surface is covered.
- **Combined-union public-project gate**: 75% (`scripts/pipeline/stage_01_test.py --project-only --all-projects --public-projects`; `DEFAULT_FAIL_UNDER` in `infrastructure/core/test_runner.py`). Deliberately lower than the per-project floor: per-project suites only cover their own `src/`, so the union denominator spans the public exemplar source set. Local `--all-projects` without `--public-projects` still runs every discovered project in the checkout and may include rotating private symlinks. Per-project floors are unchanged and remain authoritative.
- **No mocks**: All tests use real numerical examples
- **Deterministic**: Fixed RNG seeds for reproducibility
### Running Tests
```bash
# All tests
uv run python scripts/pipeline/stage_01_test.py --project {project_name}
# With coverage report
uv run pytest tests/infra_tests/ --cov=infrastructure --cov-report=html
uv run pytest projects/{name}/tests/ --cov=projects/{name}/src --cov-report=html
# Specific test
uv run pytest tests/infra_tests/test_specific.py::test_function -v
```
## Configuration
### Project Metadata (`projects/{name}/manuscript/config.yaml`)
```yaml
paper:
title: "Your Research Title"
version: "1.0"
authors:
- name: "Author Name"
orcid: "0000-0000-0000-0000"
email: "author@example.com"
affiliation: "Institution"
corresponding: true
publication:
doi: "10.5281/zenodo.12345678" # Optional
keywords:
- "keyword1"
- "keyword2"
llm:
translations:
enabled: true
languages: [zh, hi, ru]
```
### Environment Variables
- `LOG_LEVEL` - Logging verbosity (0=DEBUG, 1=INFO, 2=WARN, 3=ERROR)
- `AUTHOR_NAME` - Override config file author
- `PROJECT_TITLE` - Override config file title
- `MPLBACKEND=Agg` - Headless matplotlib (automatically set)
### IDE Integration
```bash
# Set Python path for IDE/editor integration
export PYTHONPATH=".:infrastructure:projects/templates/template_code_project/src"
```
## Development Workflow
### Adding Features
1. Write tests first (TDD) in `projects/{name}/tests/` or `tests/infra_tests/`
2. Implement in `projects/{name}/src/` or `infrastructure/`
3. Ensure coverage requirements met
4. Update documentation if needed
5. Run full pipeline to validate
### Creating New Projects
```bash
# Create project structure
mkdir -p projects/my_project/{src,tests,scripts,manuscript}
touch projects/my_project/src/__init__.py
touch projects/my_project/tests/__init__.py
# Copy config template
cp projects/templates/template_code_project/manuscript/config.yaml projects/my_project/manuscript/
# Create pyproject.toml (see existing projects for template)
# Run pipeline
./run.sh --project my_project --pipeline
```
### Working with Scripts
Scripts in `projects/{name}/scripts/` should:
- Import from `projects/{name}/src/` for computation
- Import from `infrastructure/` for utilities
- Handle only I/O, visualization, and orchestration
- Print output paths to stdout for manifest collection
- Use `MPLBACKEND=Agg` for headless plotting
- Generate deterministic outputs with fixed seeds
## Key Architectural Principles
1. **Single Source of Truth**: Business logic lives only in `infrastructure/` or `projects/{name}/src/`
2. **Test-Driven Development**: 90%+ coverage enforced before PDF generation
3. **Thin Orchestrator Pattern**: Scripts coordinate, modules implement
4. **Real-first tests**: mock frameworks are prohibited; semantic stand-ins are inventoried and tracked as migration debt
5. **Multi-Project Support**: One repository, multiple independent projects
6. **Reproducibility**: Deterministic outputs with fixed seeds
7. **Disposable Outputs**: Everything in `output/` is regeneratable
## Common Patterns
### Adding a New Analysis Script
```python
#!/usr/bin/env python3
"""Analysis script following thin orchestrator pattern."""
from pathlib import Path
from projects.my_project.src.analysis import run_analysis
from infrastructure.core.logging.utils import get_logger
logger = get_logger(__name__)
def main():
output_dir = Path("projects/my_project/output/figures")
output_dir.mkdir(parents=True, exist_ok=True)
# Use project methods for computation
results = run_analysis() # From src/
# Script handles visualization only
import matplotlib.pyplot as plt
plt.figure()
plt.plot(results)
output_path = output_dir / "analysis.png"
plt.savefig(output_path)
# Print path for manifest collection
print(str(output_path))
if __name__ == "__main__":
main()
```
### Adding a Test
```python
#!/usr/bin/env python3
"""Test following no-mocks policy."""
import pytest
from pathlib import Path
from projects.my_project.src.analysis import run_analysis
def test_analysis_produces_correct_output(tmp_path):
"""Test with data and computation."""
# Use data
input_data = [1.0, 2.0, 3.0, 4.0, 5.0]
# Execute computation
result = run_analysis(input_data)
# Validate real output
assert len(result) == 5
assert abs(result[0] - 1.0) < 1e-6
```
### Adding Infrastructure Module
```python
#!/usr/bin/env python3
"""New infrastructure module.
All infrastructure modules must:
1. Have docstrings
2. Include type hints on all public APIs
3. Be generic and reusable across projects
4. Have 60%+ test coverage
"""
from pathlib import Path
from typing import List
def process_files(input_dir: Path) -> List[Path]:
"""Process files in directory.
Args:
input_dir: Directory containing files to process
Returns:
List of processed file paths
"""
# Implementation
pass
```
## Troubleshooting
### Common Issues
**Tests Failing**: Check coverage requirements met (60% infra, 90% project)
```bash
uv run pytest --cov=infrastructure --cov-report=term-missing
```
**PDF Generation Fails**: Validate LaTeX packages
```bash
uv run python -m infrastructure.rendering.latex_package_validator
sudo tlmgr install multirow cleveref doi newunicodechar
```
**Import Errors**: Ensure project structure correct
```bash
uv run python -c "import sys; sys.path.insert(0, 'projects/{name}/src'); import {module}"
```
**Markdown Validation Errors**: Check image paths and references
```bash
uv run python -m infrastructure.validation.cli markdown projects/{name}/manuscript/
```
### Debug Mode
```bash
export LOG_LEVEL=0 # Enable debug logging
uv run python scripts/pipeline/stage_03_render.py --project {name}
```
## Documentation Resources
- **README.md** - Project overview and quick start
- **.cursorrules** - Cursor agent rules (overlap with this file on commands and architecture)
- **AGENTS.md** - System reference (configuration, modules, troubleshooting details)
- **.github/README.md** / **.github/AGENTS.md** - CI workflows, Dependabot, PR templates; local parity via **`.pre-commit-config.yaml`**
- **[docs/RUN_GUIDE.md](docs/RUN_GUIDE.md)** - Pipeline execution documentation
- **docs/core/architecture.md** - Detailed architecture guide
- **docs/core/workflow.md** - Development workflow details
- **docs/core/how-to-use.md** - Usage guide (12 skill levels)
- **docs/documentation-index.md** - Curated documentation map (by category; not an exhaustive listing of every tracked file)
## Important Notes
A hand-edited output is the known-wrong case: regeneration overwrites the edit and the resulting diff exposes the tampering.
- Generated outputs are regeneratable and must never be hand-edited to pass a gate; a stale hand-edit is expected to fail the `docs-contract-guard` (`docgen/* --check`) rather than pass. The generated-artifact guard rejects force-added generated paths, so a hand-edited artifact cannot slip past CI. (negative control: the generated-artifact guard deliberately force-adds `.codegraph/` content in `tests/infra_tests/project/` fixtures and asserts rejection, so weakening or deleting the guard is itself caught)
This prohibition is enforced, not aspirational: regenerated-artifact guards reject tracked files claiming to be generated state, and their test fixtures feed deliberately regenerated artifacts to assert that rejection.
- Canonical public exemplar outputs admitted by the repository allowlist are tracked publication evidence; local/private/fork outputs remain ignored.
- Install **pre-commit** hooks after `uv sync` so Ruff, mypy, Bandit, and push-time checks run locally (see `.pre-commit-config.yaml`)
- Always run tests before committing changes
- Follow thin orchestrator pattern strictly
- No mocks allowed in tests (use `pytest-httpserver` for HTTP, real files for I/O)
- Maintain 90%+ test coverage for project code, 60%+ for infrastructure
- Use `uv` for dependency management (recommended)
- Pipeline can be resumed from checkpoints with `--resume`
- Tests timeout after 10 seconds by default (configurable in pyproject.toml)
<!-- BEGIN:STAGE_SUMMARY -->
The default [`pipeline.yaml`](infrastructure/core/pipeline/pipeline.yaml) declares **17 named stages** (indices 0–16). Default full runs execute **10** core+LLM stages; `--core-only` executes **8**. Opt-in tags (`archival`, `bundle`, `docxplus`, `ebook`, `metadata`, `provenance`, `science`) stay out of those default runs unless a stage is invoked directly. YAML stage indices do not match `stage_NN_*.py` prefixes.
<!-- END:STAGE_SUMMARY -->
<!-- BEGIN:STAGE_TABLE -->
<!-- This block is generated from [`infrastructure/core/pipeline/pipeline.yaml`](infrastructure/core/pipeline/pipeline.yaml) by `scripts/docgen/stage_table.py`. Do not hand-edit. Stage indices are **0-based positions in the YAML** and intentionally do **not** match the `scripts/pipeline/stage_NN_*.py` numeric prefixes (for example, stage 11, "Copy Outputs", runs `scripts/pipeline/stage_05_copy.py`). -->
| Stage | Script | Tags | Failure mode |
| ----- | ------ | ---- | ------------ |
| **0** Clean Output Directories | built-in `_run_clean_outputs` | `core`, `clean` | soft fail |
| **1** Environment Setup | `scripts/pipeline/stage_00_setup.py` | `core` | hard fail |
| **2** Infrastructure Tests | `scripts/pipeline/stage_01_test.py --infra-only --verbose --infra-scope pipeline-smoke` | `core`, `tests` | configurable tolerance |
| **3** Project Tests | `scripts/pipeline/stage_01_test.py --project-only --verbose` | `core`, `tests` | configurable test-failure tolerance; zero-test, project-local coverage, verifier-receipt/evidence, and internal runner failures hard fail |
| **4** Project Analysis | `scripts/pipeline/stage_02_analysis.py` | `core` | hard fail |
| **5** Connector Search | `scripts/pipeline/stage_08_connector_search.py` | `science` | skipped if not configured |
| **6** Provenance Record | `scripts/pipeline/stage_09_provenance_record.py --stage Connector Search` | `provenance` | skipped if not configured |
| **7** PDF Rendering | `scripts/pipeline/stage_03_render.py` | `core` | hard fail |
| **8** Output Validation | `scripts/pipeline/stage_04_validate.py` | `core` | enabled-format, enabled-PDF bookend, and artifact/provenance failures block; markdown, general output structure, and prose-quality checks remain advisory |
| **9** LLM Scientific Review | `scripts/pipeline/stage_06_llm_review.py --reviews-only` | `llm` | skipped if Ollama absent |
| **10** LLM Translations | `scripts/pipeline/stage_06_llm_review.py --translations-only` | `llm` | skipped if Ollama absent |
| **11** Copy Outputs | `scripts/pipeline/stage_05_copy.py` | `core` | soft fail |
| **12** Ebook Generation | `scripts/pipeline/stage_11_ebook.py` | `core`, `ebook` | soft fail |
| **13** docxplus Export | `scripts/pipeline/stage_13_docxplus.py` | `core`, `docxplus` | soft fail |
| **14** Metadata Package | `scripts/pipeline/stage_12_metadata.py` | `core`, `metadata` | soft fail |
| **15** Executable Bundle | `scripts/runner/bundle_executable.py` | `bundle` | soft fail |
| **16** Archival Publication | `scripts/runner/archive_publication.py` | `archival` | soft fail |
<!-- END:STAGE_TABLE -->