earthforge 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- earthforge-0.1.0/.github/copilot-instructions.md +72 -0
- earthforge-0.1.0/.github/workflows/ci.yml +189 -0
- earthforge-0.1.0/.github/workflows/docs.yml +52 -0
- earthforge-0.1.0/.github/workflows/prompt-eval.yml +152 -0
- earthforge-0.1.0/.github/workflows/publish.yml +149 -0
- earthforge-0.1.0/.gitignore +31 -0
- earthforge-0.1.0/=1.7 +145 -0
- earthforge-0.1.0/ARCHITECTURE.md +88 -0
- earthforge-0.1.0/CHANGELOG.md +105 -0
- earthforge-0.1.0/CLAUDE.md +196 -0
- earthforge-0.1.0/CONTRIBUTING.md +130 -0
- earthforge-0.1.0/LICENSE +674 -0
- earthforge-0.1.0/PKG-INFO +273 -0
- earthforge-0.1.0/README.md +227 -0
- earthforge-0.1.0/ai-dev/agents/README.md +25 -0
- earthforge-0.1.0/ai-dev/agents/architect.md +48 -0
- earthforge-0.1.0/ai-dev/agents/gis_domain_expert.md +97 -0
- earthforge-0.1.0/ai-dev/agents/python_expert.md +232 -0
- earthforge-0.1.0/ai-dev/architecture.md +343 -0
- earthforge-0.1.0/ai-dev/decisions/DL-001-monorepo.md +34 -0
- earthforge-0.1.0/ai-dev/decisions/DL-002-async-first-io.md +31 -0
- earthforge-0.1.0/ai-dev/decisions/DL-003-storage-abstraction.md +27 -0
- earthforge-0.1.0/ai-dev/decisions/DL-004-output-contract.md +74 -0
- earthforge-0.1.0/ai-dev/decisions/DL-005-rust-boundary.md +48 -0
- earthforge-0.1.0/ai-dev/decisions/DL-006-engineering-credibility.md +52 -0
- earthforge-0.1.0/ai-dev/decisions/DL-007-promptfoo-eval.md +36 -0
- earthforge-0.1.0/ai-dev/guardrails/README.md +11 -0
- earthforge-0.1.0/ai-dev/guardrails/cloud-native-compliance.md +37 -0
- earthforge-0.1.0/ai-dev/guardrails/coding-standards.md +64 -0
- earthforge-0.1.0/ai-dev/guardrails/data-handling.md +21 -0
- earthforge-0.1.0/ai-dev/patterns.md +69 -0
- earthforge-0.1.0/ai-dev/prompt-templates.md +71 -0
- earthforge-0.1.0/ai-dev/skills/README.md +15 -0
- earthforge-0.1.0/ai-dev/skills/cloud-native-formats.md +134 -0
- earthforge-0.1.0/ai-dev/spec.md +112 -0
- earthforge-0.1.0/ai-dev/test-data-plan.md +216 -0
- earthforge-0.1.0/ai-dev/validation-reports/README.md +91 -0
- earthforge-0.1.0/ai-dev/validation-reports/VR-M0-format-detection.md +57 -0
- earthforge-0.1.0/ai-dev/validation-reports/VR-M0-raster-info.md +40 -0
- earthforge-0.1.0/ai-dev/validation-reports/VR-M0-vector-info.md +42 -0
- earthforge-0.1.0/ai-dev/validation-reports/VR-M1-kygeonet-integration.md +81 -0
- earthforge-0.1.0/ai-dev/validation-reports/VR-M1-raster-preview.md +46 -0
- earthforge-0.1.0/ai-dev/validation-reports/VR-M1-raster-validate.md +43 -0
- earthforge-0.1.0/ai-dev/validation-reports/VR-M2-raster-convert.md +54 -0
- earthforge-0.1.0/ai-dev/validation-reports/VR-M2-vector-convert.md +47 -0
- earthforge-0.1.0/ai-dev/validation-reports/VR-M2-vector-query.md +52 -0
- earthforge-0.1.0/ai-dev/validation-reports/VR-M3-cube-info.md +90 -0
- earthforge-0.1.0/ai-dev/validation-reports/VR-M3-pipeline-run.md +115 -0
- earthforge-0.1.0/ai-dev/validation-reports/VR-M3-stac-fetch.md +65 -0
- earthforge-0.1.0/branding/earthforge-banner.png +0 -0
- earthforge-0.1.0/data/.gitignore +11 -0
- earthforge-0.1.0/data/samples/kyfromabove_preview.png +0 -0
- earthforge-0.1.0/data/samples/raster_info.json +27 -0
- earthforge-0.1.0/data/samples/stac_fetch.json +21 -0
- earthforge-0.1.0/data/samples/stac_search.json +39 -0
- earthforge-0.1.0/data/samples/vector_info.json +21 -0
- earthforge-0.1.0/docs/api/reference.md +85 -0
- earthforge-0.1.0/docs/architecture.md +126 -0
- earthforge-0.1.0/docs/cli.md +314 -0
- earthforge-0.1.0/docs/getting-started.md +155 -0
- earthforge-0.1.0/docs/index.md +118 -0
- earthforge-0.1.0/docs/release-notes.md +66 -0
- earthforge-0.1.0/docs/stac-collections.md +133 -0
- earthforge-0.1.0/docs/tutorials/arcgis-pro.md +167 -0
- earthforge-0.1.0/docs/tutorials/dem-hillshade.md +149 -0
- earthforge-0.1.0/docs/tutorials/lidar-access.md +136 -0
- earthforge-0.1.0/docs/tutorials/titiler-maps.md +144 -0
- earthforge-0.1.0/evals/README.md +60 -0
- earthforge-0.1.0/evals/assertions/check_async_pattern.js +23 -0
- earthforge-0.1.0/evals/assertions/check_error_handling.js +28 -0
- earthforge-0.1.0/evals/assertions/check_no_direct_imports.js +43 -0
- earthforge-0.1.0/evals/assertions/check_structured_return.js +24 -0
- earthforge-0.1.0/evals/promptfooconfig.redteam.yaml +172 -0
- earthforge-0.1.0/evals/promptfooconfig.templates.yaml +66 -0
- earthforge-0.1.0/evals/promptfooconfig.yaml +142 -0
- earthforge-0.1.0/evals/prompts/python_expert_implement.txt +54 -0
- earthforge-0.1.0/evals/prompts/redteam_system.txt +45 -0
- earthforge-0.1.0/evals/prompts/template_implement_feature.txt +11 -0
- earthforge-0.1.0/examples/notebooks/01_getting_started.ipynb +206 -0
- earthforge-0.1.0/examples/notebooks/02_stac_search.ipynb +316 -0
- earthforge-0.1.0/examples/notebooks/03_vector_operations.ipynb +289 -0
- earthforge-0.1.0/examples/scripts/cube_info_era5_demo.py +81 -0
- earthforge-0.1.0/examples/scripts/ky_wma_demo.py +238 -0
- earthforge-0.1.0/examples/scripts/kygeonet_stac_demo.py +145 -0
- earthforge-0.1.0/examples/scripts/ndvi_pipeline.yaml +55 -0
- earthforge-0.1.0/examples/scripts/stac_fetch_kyfromabove_demo.py +149 -0
- earthforge-0.1.0/examples/web/docker-compose.yml +44 -0
- earthforge-0.1.0/examples/web/titiler_map.html +303 -0
- earthforge-0.1.0/mkdocs.yml +94 -0
- earthforge-0.1.0/packages/cli/README.md +5 -0
- earthforge-0.1.0/packages/cli/pyproject.toml +23 -0
- earthforge-0.1.0/packages/cli/src/earthforge/cli/__init__.py +6 -0
- earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/__init__.py +5 -0
- earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/bench_cmd.py +215 -0
- earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/completions_cmd.py +92 -0
- earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/config_cmd.py +69 -0
- earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/cube_cmd.py +104 -0
- earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/explore_cmd.py +98 -0
- earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/info.py +136 -0
- earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/pipeline_cmd.py +107 -0
- earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/raster_cmd.py +121 -0
- earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/stac_cmd.py +163 -0
- earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/vector_cmd.py +126 -0
- earthforge-0.1.0/packages/cli/src/earthforge/cli/main.py +181 -0
- earthforge-0.1.0/packages/cli/src/earthforge/cli/tui/__init__.py +7 -0
- earthforge-0.1.0/packages/cli/src/earthforge/cli/tui/app.py +368 -0
- earthforge-0.1.0/packages/cli/tests/test_explore_cmd.py +194 -0
- earthforge-0.1.0/packages/cli/tests/test_main.py +149 -0
- earthforge-0.1.0/packages/core/README.md +5 -0
- earthforge-0.1.0/packages/core/pyproject.toml +21 -0
- earthforge-0.1.0/packages/core/src/earthforge/core/__init__.py +8 -0
- earthforge-0.1.0/packages/core/src/earthforge/core/config.py +239 -0
- earthforge-0.1.0/packages/core/src/earthforge/core/errors.py +85 -0
- earthforge-0.1.0/packages/core/src/earthforge/core/formats.py +440 -0
- earthforge-0.1.0/packages/core/src/earthforge/core/http.py +205 -0
- earthforge-0.1.0/packages/core/src/earthforge/core/output.py +206 -0
- earthforge-0.1.0/packages/core/src/earthforge/core/storage.py +272 -0
- earthforge-0.1.0/packages/core/tests/test_config.py +185 -0
- earthforge-0.1.0/packages/core/tests/test_errors.py +90 -0
- earthforge-0.1.0/packages/core/tests/test_formats.py +260 -0
- earthforge-0.1.0/packages/core/tests/test_http.py +160 -0
- earthforge-0.1.0/packages/core/tests/test_output.py +203 -0
- earthforge-0.1.0/packages/core/tests/test_storage.py +147 -0
- earthforge-0.1.0/packages/cube/README.md +8 -0
- earthforge-0.1.0/packages/cube/pyproject.toml +21 -0
- earthforge-0.1.0/packages/cube/src/earthforge/cube/__init__.py +7 -0
- earthforge-0.1.0/packages/cube/src/earthforge/cube/errors.py +15 -0
- earthforge-0.1.0/packages/cube/src/earthforge/cube/info.py +415 -0
- earthforge-0.1.0/packages/cube/src/earthforge/cube/py.typed +0 -0
- earthforge-0.1.0/packages/cube/src/earthforge/cube/slice.py +344 -0
- earthforge-0.1.0/packages/cube/tests/conftest.py +15 -0
- earthforge-0.1.0/packages/cube/tests/test_cube_info.py +236 -0
- earthforge-0.1.0/packages/cube/tests/test_cube_slice.py +227 -0
- earthforge-0.1.0/packages/pipeline/README.md +10 -0
- earthforge-0.1.0/packages/pipeline/pyproject.toml +19 -0
- earthforge-0.1.0/packages/pipeline/src/earthforge/pipeline/__init__.py +29 -0
- earthforge-0.1.0/packages/pipeline/src/earthforge/pipeline/errors.py +28 -0
- earthforge-0.1.0/packages/pipeline/src/earthforge/pipeline/py.typed +0 -0
- earthforge-0.1.0/packages/pipeline/src/earthforge/pipeline/runner.py +356 -0
- earthforge-0.1.0/packages/pipeline/src/earthforge/pipeline/schema.py +119 -0
- earthforge-0.1.0/packages/pipeline/src/earthforge/pipeline/steps.py +479 -0
- earthforge-0.1.0/packages/pipeline/src/earthforge/pipeline/template.py +81 -0
- earthforge-0.1.0/packages/pipeline/tests/conftest.py +13 -0
- earthforge-0.1.0/packages/pipeline/tests/test_pipeline_runner.py +249 -0
- earthforge-0.1.0/packages/pipeline/tests/test_pipeline_schema.py +141 -0
- earthforge-0.1.0/packages/pipeline/tests/test_pipeline_steps.py +206 -0
- earthforge-0.1.0/packages/raster/README.md +5 -0
- earthforge-0.1.0/packages/raster/pyproject.toml +19 -0
- earthforge-0.1.0/packages/raster/src/earthforge/raster/__init__.py +7 -0
- earthforge-0.1.0/packages/raster/src/earthforge/raster/convert.py +261 -0
- earthforge-0.1.0/packages/raster/src/earthforge/raster/errors.py +29 -0
- earthforge-0.1.0/packages/raster/src/earthforge/raster/info.py +214 -0
- earthforge-0.1.0/packages/raster/src/earthforge/raster/preview.py +181 -0
- earthforge-0.1.0/packages/raster/src/earthforge/raster/validate.py +195 -0
- earthforge-0.1.0/packages/raster/tests/test_raster_convert.py +179 -0
- earthforge-0.1.0/packages/raster/tests/test_raster_info.py +141 -0
- earthforge-0.1.0/packages/raster/tests/test_raster_preview.py +130 -0
- earthforge-0.1.0/packages/raster/tests/test_raster_validate.py +168 -0
- earthforge-0.1.0/packages/stac/README.md +3 -0
- earthforge-0.1.0/packages/stac/pyproject.toml +19 -0
- earthforge-0.1.0/packages/stac/src/earthforge/stac/__init__.py +6 -0
- earthforge-0.1.0/packages/stac/src/earthforge/stac/errors.py +33 -0
- earthforge-0.1.0/packages/stac/src/earthforge/stac/fetch.py +328 -0
- earthforge-0.1.0/packages/stac/src/earthforge/stac/info.py +210 -0
- earthforge-0.1.0/packages/stac/src/earthforge/stac/search.py +256 -0
- earthforge-0.1.0/packages/stac/tests/test_stac_fetch.py +277 -0
- earthforge-0.1.0/packages/stac/tests/test_stac_info.py +210 -0
- earthforge-0.1.0/packages/stac/tests/test_stac_search.py +213 -0
- earthforge-0.1.0/packages/vector/README.md +3 -0
- earthforge-0.1.0/packages/vector/pyproject.toml +18 -0
- earthforge-0.1.0/packages/vector/src/earthforge/vector/__init__.py +6 -0
- earthforge-0.1.0/packages/vector/src/earthforge/vector/convert.py +356 -0
- earthforge-0.1.0/packages/vector/src/earthforge/vector/errors.py +21 -0
- earthforge-0.1.0/packages/vector/src/earthforge/vector/info.py +245 -0
- earthforge-0.1.0/packages/vector/src/earthforge/vector/query.py +384 -0
- earthforge-0.1.0/packages/vector/tests/test_vector_convert.py +226 -0
- earthforge-0.1.0/packages/vector/tests/test_vector_info.py +174 -0
- earthforge-0.1.0/packages/vector/tests/test_vector_query.py +176 -0
- earthforge-0.1.0/pyproject.toml +140 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Copilot Instructions — EarthForge
|
|
2
|
+
|
|
3
|
+
> Cloud-Native Geospatial Developer Toolkit
|
|
4
|
+
> Python 3.11+ · Hatch · Typer · PyO3/maturin · httpx · obstore
|
|
5
|
+
|
|
6
|
+
## Teach-As-You-Build Protocol
|
|
7
|
+
|
|
8
|
+
After completing each component (file, module, function, config block), immediately explain:
|
|
9
|
+
1. What you just wrote (one sentence)
|
|
10
|
+
2. Why it exists and why you made the specific choices you made
|
|
11
|
+
3. How it connects to the rest of the system
|
|
12
|
+
4. What to watch for (common mistakes, edge cases)
|
|
13
|
+
|
|
14
|
+
Write explanations as narrative for a senior developer who knows GIS and Python but is encountering this architecture for the first time. Do not oversimplify. After explaining, ask: "Ready for the next component, or questions on this one?"
|
|
15
|
+
|
|
16
|
+
Do not batch explanations. Explain each component immediately after writing it.
|
|
17
|
+
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
- **Monorepo** with Hatch workspace packages under `packages/`
|
|
21
|
+
- **Library-first, CLI-second**: Business logic in domain packages, CLI is thin dispatch
|
|
22
|
+
- **Async-first I/O**: httpx for HTTP, obstore for cloud storage, asyncio.run() at CLI entry
|
|
23
|
+
- **Namespace packages**: No `earthforge/__init__.py` — packages merge via PEP 420
|
|
24
|
+
- **Structured output**: Commands return Pydantic models, output module renders json/table/csv
|
|
25
|
+
|
|
26
|
+
## Packages
|
|
27
|
+
|
|
28
|
+
| Package | Role | Key Dependencies |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `core` | Config, storage, output, format detection, error types | httpx, obstore, pydantic, rich |
|
|
31
|
+
| `cli` | Typer CLI, argument parsing, output formatting | typer, core |
|
|
32
|
+
| `stac` | STAC search, info, validate, fetch | pystac-client, core |
|
|
33
|
+
| `raster` | COG info, validate, convert, preview, band math | rasterio, numpy, Pillow, core |
|
|
34
|
+
| `vector` | GeoParquet info, validate, convert, query | geopandas, pyarrow, core |
|
|
35
|
+
| `cube` | Zarr/NetCDF info, validate, convert, slice | xarray, zarr, core |
|
|
36
|
+
| `rs` | Rust acceleration (format detection, range reads) | PyO3, maturin (NOT hatchling) |
|
|
37
|
+
|
|
38
|
+
## Hard Rules
|
|
39
|
+
|
|
40
|
+
- All I/O through `earthforge.core.http` or `earthforge.core.storage` — never raw httpx/obstore
|
|
41
|
+
- All output through `earthforge.core.output` — never `print()`
|
|
42
|
+
- All exceptions inherit `earthforge.core.errors.EarthForgeError`
|
|
43
|
+
- No business logic in CLI layer
|
|
44
|
+
- No `eval()`/`exec()` for expressions
|
|
45
|
+
- No hardcoded URLs or credentials — use `earthforge.core.config` profiles
|
|
46
|
+
- Rust extension: always provide pure-Python fallback via try/except import
|
|
47
|
+
- `packages/rs/` uses maturin build backend, not hatchling
|
|
48
|
+
- No empty files, skeleton directories, or TODO-only stubs — if it's in the repo, it works
|
|
49
|
+
- No single "initial commit" dumps — build incrementally, one logical change per commit
|
|
50
|
+
- No AI-generated code committed without the contributor understanding every line
|
|
51
|
+
|
|
52
|
+
## Git Conventions
|
|
53
|
+
|
|
54
|
+
Conventional Commits with package scope:
|
|
55
|
+
```
|
|
56
|
+
feat(core): add format detection chain with magic byte sniffing
|
|
57
|
+
fix(stac): handle pagination for STAC APIs without next link
|
|
58
|
+
test(raster): add COG validation tests for untiled GeoTIFF
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Every new module ships with tests. A module without tests is not complete.
|
|
62
|
+
|
|
63
|
+
## Real-World Validation
|
|
64
|
+
|
|
65
|
+
Every feature must be tested against the real-world datasets in `ai-dev/test-data-plan.md` before it ships. Record results in `ai-dev/validation-reports/VR-{milestone}-{feature}.md`. A feature without a validation report is not complete.
|
|
66
|
+
|
|
67
|
+
## Read First
|
|
68
|
+
|
|
69
|
+
- `CLAUDE.md` — full project context
|
|
70
|
+
- `ai-dev/architecture.md` — system design
|
|
71
|
+
- `ai-dev/guardrails/` — constraints that override all other guidance
|
|
72
|
+
- `ai-dev/decisions/` — settled architectural decisions
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
# Cancel in-progress runs for the same branch on new push
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
# Lint — ruff only, no geo deps, completes in ~10s
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
lint:
|
|
19
|
+
name: Lint
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- uses: actions/setup-python@v5
|
|
26
|
+
with:
|
|
27
|
+
python-version: "3.11"
|
|
28
|
+
|
|
29
|
+
- name: Install ruff
|
|
30
|
+
run: pip install "ruff>=0.4"
|
|
31
|
+
|
|
32
|
+
- name: ruff check
|
|
33
|
+
run: ruff check .
|
|
34
|
+
|
|
35
|
+
- name: ruff format
|
|
36
|
+
run: ruff format --check .
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Type check — mypy strict
|
|
40
|
+
# ignore_missing_imports covers rasterio/pyarrow/obstore stubs so
|
|
41
|
+
# no conda / heavy geo deps needed here.
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
typecheck:
|
|
44
|
+
name: Type Check
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v4
|
|
49
|
+
|
|
50
|
+
- uses: actions/setup-python@v5
|
|
51
|
+
with:
|
|
52
|
+
python-version: "3.11"
|
|
53
|
+
|
|
54
|
+
- name: Install Python dependencies
|
|
55
|
+
run: |
|
|
56
|
+
pip install \
|
|
57
|
+
"httpx>=0.27" \
|
|
58
|
+
"pydantic>=2.0" \
|
|
59
|
+
"rich>=13.0" \
|
|
60
|
+
"orjson>=3.9" \
|
|
61
|
+
"typer>=0.9" \
|
|
62
|
+
"pystac>=1.9" \
|
|
63
|
+
"pystac-client>=0.8" \
|
|
64
|
+
"numpy>=1.24" \
|
|
65
|
+
"mypy>=1.10"
|
|
66
|
+
|
|
67
|
+
- name: Install earthforge packages (editable, no geo deps)
|
|
68
|
+
run: |
|
|
69
|
+
pip install -e packages/core
|
|
70
|
+
pip install -e packages/stac
|
|
71
|
+
pip install -e packages/raster
|
|
72
|
+
pip install -e packages/vector
|
|
73
|
+
pip install -e packages/cli
|
|
74
|
+
|
|
75
|
+
- name: mypy strict
|
|
76
|
+
run: |
|
|
77
|
+
mypy \
|
|
78
|
+
packages/core/src \
|
|
79
|
+
packages/stac/src \
|
|
80
|
+
packages/raster/src \
|
|
81
|
+
packages/vector/src \
|
|
82
|
+
packages/cli/src
|
|
83
|
+
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
# Test — pytest unit tests (no network calls)
|
|
86
|
+
# Uses conda for GDAL, rasterio, pyarrow — these are difficult to install
|
|
87
|
+
# reliably via pip on CI runners. conda-forge gives consistent binary builds.
|
|
88
|
+
# Integration-marked tests (real network) are excluded from CI runs.
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
test:
|
|
91
|
+
name: Test (Python ${{ matrix.python-version }})
|
|
92
|
+
runs-on: ubuntu-latest
|
|
93
|
+
defaults:
|
|
94
|
+
run:
|
|
95
|
+
shell: bash -el {0}
|
|
96
|
+
|
|
97
|
+
strategy:
|
|
98
|
+
fail-fast: false
|
|
99
|
+
matrix:
|
|
100
|
+
python-version: ["3.11", "3.12"]
|
|
101
|
+
|
|
102
|
+
steps:
|
|
103
|
+
- uses: actions/checkout@v4
|
|
104
|
+
|
|
105
|
+
- uses: conda-incubator/setup-miniconda@v3
|
|
106
|
+
with:
|
|
107
|
+
python-version: ${{ matrix.python-version }}
|
|
108
|
+
channels: conda-forge,defaults
|
|
109
|
+
channel-priority: strict
|
|
110
|
+
activate-environment: earthforge-ci
|
|
111
|
+
|
|
112
|
+
- name: Install geospatial dependencies via conda
|
|
113
|
+
run: conda install -y gdal rasterio pyarrow
|
|
114
|
+
|
|
115
|
+
- name: Install Python dependencies via pip
|
|
116
|
+
run: |
|
|
117
|
+
pip install \
|
|
118
|
+
"httpx>=0.27" \
|
|
119
|
+
"pydantic>=2.0" \
|
|
120
|
+
"rich>=13.0" \
|
|
121
|
+
"orjson>=3.9" \
|
|
122
|
+
"typer>=0.9" \
|
|
123
|
+
"pystac>=1.9" \
|
|
124
|
+
"pystac-client>=0.8" \
|
|
125
|
+
"numpy>=1.24" \
|
|
126
|
+
"obstore>=0.3" \
|
|
127
|
+
"respx>=0.21" \
|
|
128
|
+
"pytest>=8.0" \
|
|
129
|
+
"pytest-asyncio>=0.23" \
|
|
130
|
+
"pytest-cov>=4.0" \
|
|
131
|
+
"coverage>=7.0"
|
|
132
|
+
|
|
133
|
+
- name: Install earthforge packages (editable)
|
|
134
|
+
run: |
|
|
135
|
+
pip install -e packages/core
|
|
136
|
+
pip install -e packages/stac
|
|
137
|
+
pip install -e packages/raster
|
|
138
|
+
pip install -e packages/vector
|
|
139
|
+
pip install -e packages/cli
|
|
140
|
+
|
|
141
|
+
- name: Run unit tests (no network)
|
|
142
|
+
run: |
|
|
143
|
+
pytest -m "not integration" \
|
|
144
|
+
--tb=short \
|
|
145
|
+
-q \
|
|
146
|
+
--cov=packages/core/src \
|
|
147
|
+
--cov=packages/stac/src \
|
|
148
|
+
--cov=packages/raster/src \
|
|
149
|
+
--cov=packages/vector/src \
|
|
150
|
+
--cov-report=term-missing \
|
|
151
|
+
--cov-report=xml
|
|
152
|
+
|
|
153
|
+
- name: Upload coverage
|
|
154
|
+
uses: actions/upload-artifact@v4
|
|
155
|
+
if: matrix.python-version == '3.11'
|
|
156
|
+
with:
|
|
157
|
+
name: coverage-report
|
|
158
|
+
path: coverage.xml
|
|
159
|
+
|
|
160
|
+
# ---------------------------------------------------------------------------
|
|
161
|
+
# Build — hatch build for each package
|
|
162
|
+
# Verifies every package produces a valid wheel. Does not publish.
|
|
163
|
+
# ---------------------------------------------------------------------------
|
|
164
|
+
build:
|
|
165
|
+
name: Build
|
|
166
|
+
runs-on: ubuntu-latest
|
|
167
|
+
|
|
168
|
+
steps:
|
|
169
|
+
- uses: actions/checkout@v4
|
|
170
|
+
|
|
171
|
+
- uses: actions/setup-python@v5
|
|
172
|
+
with:
|
|
173
|
+
python-version: "3.11"
|
|
174
|
+
|
|
175
|
+
- name: Install hatch
|
|
176
|
+
run: pip install "hatch>=1.7"
|
|
177
|
+
|
|
178
|
+
- name: Build all packages
|
|
179
|
+
run: |
|
|
180
|
+
for pkg in packages/core packages/stac packages/raster packages/vector packages/cli; do
|
|
181
|
+
echo "── Building $pkg ──"
|
|
182
|
+
(cd "$pkg" && hatch build -t wheel)
|
|
183
|
+
done
|
|
184
|
+
|
|
185
|
+
- name: Upload wheels
|
|
186
|
+
uses: actions/upload-artifact@v4
|
|
187
|
+
with:
|
|
188
|
+
name: wheels
|
|
189
|
+
path: packages/*/dist/*.whl
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: Docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
deploy:
|
|
13
|
+
name: Build and deploy docs
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 0 # full history required for git-revision-date plugin
|
|
20
|
+
|
|
21
|
+
- uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.11"
|
|
24
|
+
|
|
25
|
+
- name: Install docs dependencies
|
|
26
|
+
run: |
|
|
27
|
+
pip install \
|
|
28
|
+
"mkdocs-material>=9.5" \
|
|
29
|
+
"mkdocstrings[python]>=0.25" \
|
|
30
|
+
"pymdown-extensions>=10.0"
|
|
31
|
+
|
|
32
|
+
- name: Install earthforge packages (for mkdocstrings to resolve imports)
|
|
33
|
+
run: |
|
|
34
|
+
pip install \
|
|
35
|
+
"httpx>=0.27" \
|
|
36
|
+
"pydantic>=2.0" \
|
|
37
|
+
"rich>=13.0" \
|
|
38
|
+
"orjson>=3.9" \
|
|
39
|
+
"typer>=0.9" \
|
|
40
|
+
"pystac>=1.9" \
|
|
41
|
+
"pystac-client>=0.8" \
|
|
42
|
+
"numpy>=1.24"
|
|
43
|
+
pip install -e packages/core
|
|
44
|
+
pip install -e packages/stac
|
|
45
|
+
pip install -e packages/raster
|
|
46
|
+
pip install -e packages/vector
|
|
47
|
+
pip install -e packages/cli
|
|
48
|
+
pip install -e packages/cube
|
|
49
|
+
pip install -e packages/pipeline
|
|
50
|
+
|
|
51
|
+
- name: Build and deploy to GitHub Pages
|
|
52
|
+
run: mkdocs gh-deploy --force
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
name: Prompt Eval
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
paths:
|
|
6
|
+
- 'ai-dev/agents/**'
|
|
7
|
+
- 'ai-dev/guardrails/**'
|
|
8
|
+
- 'ai-dev/prompt-templates.md'
|
|
9
|
+
- 'CLAUDE.md'
|
|
10
|
+
- '.github/copilot-instructions.md'
|
|
11
|
+
- 'evals/**'
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
eval-agents:
|
|
15
|
+
name: Agent Prompt Eval
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: actions/setup-node@v4
|
|
20
|
+
with:
|
|
21
|
+
node-version: '22'
|
|
22
|
+
|
|
23
|
+
- name: Cache promptfoo
|
|
24
|
+
uses: actions/cache@v4
|
|
25
|
+
with:
|
|
26
|
+
path: ~/.cache/promptfoo
|
|
27
|
+
key: ${{ runner.os }}-promptfoo-agents-${{ hashFiles('evals/promptfooconfig.yaml') }}
|
|
28
|
+
|
|
29
|
+
- name: Run agent evals
|
|
30
|
+
env:
|
|
31
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
32
|
+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
33
|
+
PROMPTFOO_CACHE_PATH: ~/.cache/promptfoo
|
|
34
|
+
working-directory: evals
|
|
35
|
+
run: |
|
|
36
|
+
npx promptfoo@latest eval \
|
|
37
|
+
-c promptfooconfig.yaml \
|
|
38
|
+
-o results-agents.json \
|
|
39
|
+
-o report-agents.html
|
|
40
|
+
|
|
41
|
+
- name: Check quality gate (agents)
|
|
42
|
+
working-directory: evals
|
|
43
|
+
run: |
|
|
44
|
+
FAILURES=$(jq '.results.stats.failures' results-agents.json)
|
|
45
|
+
TOTAL=$(jq '.results.stats.successes + .results.stats.failures' results-agents.json)
|
|
46
|
+
echo "Agent eval: $((TOTAL - FAILURES))/$TOTAL passed"
|
|
47
|
+
if [ "$FAILURES" -gt 0 ]; then
|
|
48
|
+
echo "::error::Agent prompt eval failed with $FAILURES failures"
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
- name: Upload agent eval results
|
|
53
|
+
if: always()
|
|
54
|
+
uses: actions/upload-artifact@v4
|
|
55
|
+
with:
|
|
56
|
+
name: eval-results-agents
|
|
57
|
+
path: |
|
|
58
|
+
evals/results-agents.json
|
|
59
|
+
evals/report-agents.html
|
|
60
|
+
|
|
61
|
+
eval-redteam:
|
|
62
|
+
name: Guardrail Red-Team
|
|
63
|
+
runs-on: ubuntu-latest
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/checkout@v4
|
|
66
|
+
- uses: actions/setup-node@v4
|
|
67
|
+
with:
|
|
68
|
+
node-version: '22'
|
|
69
|
+
|
|
70
|
+
- name: Cache promptfoo
|
|
71
|
+
uses: actions/cache@v4
|
|
72
|
+
with:
|
|
73
|
+
path: ~/.cache/promptfoo
|
|
74
|
+
key: ${{ runner.os }}-promptfoo-redteam-${{ hashFiles('evals/promptfooconfig.redteam.yaml') }}
|
|
75
|
+
|
|
76
|
+
- name: Run red-team evals
|
|
77
|
+
env:
|
|
78
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
79
|
+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
80
|
+
PROMPTFOO_CACHE_PATH: ~/.cache/promptfoo
|
|
81
|
+
working-directory: evals
|
|
82
|
+
run: |
|
|
83
|
+
npx promptfoo@latest eval \
|
|
84
|
+
-c promptfooconfig.redteam.yaml \
|
|
85
|
+
-o results-redteam.json \
|
|
86
|
+
-o report-redteam.html
|
|
87
|
+
|
|
88
|
+
- name: Check quality gate (red-team)
|
|
89
|
+
working-directory: evals
|
|
90
|
+
run: |
|
|
91
|
+
FAILURES=$(jq '.results.stats.failures' results-redteam.json)
|
|
92
|
+
TOTAL=$(jq '.results.stats.successes + .results.stats.failures' results-redteam.json)
|
|
93
|
+
echo "Red-team eval: $((TOTAL - FAILURES))/$TOTAL guardrails held"
|
|
94
|
+
if [ "$FAILURES" -gt 0 ]; then
|
|
95
|
+
echo "::error::Red-team eval: $FAILURES guardrails breached"
|
|
96
|
+
exit 1
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
- name: Upload red-team results
|
|
100
|
+
if: always()
|
|
101
|
+
uses: actions/upload-artifact@v4
|
|
102
|
+
with:
|
|
103
|
+
name: eval-results-redteam
|
|
104
|
+
path: |
|
|
105
|
+
evals/results-redteam.json
|
|
106
|
+
evals/report-redteam.html
|
|
107
|
+
|
|
108
|
+
eval-templates:
|
|
109
|
+
name: Template Consistency
|
|
110
|
+
runs-on: ubuntu-latest
|
|
111
|
+
steps:
|
|
112
|
+
- uses: actions/checkout@v4
|
|
113
|
+
- uses: actions/setup-node@v4
|
|
114
|
+
with:
|
|
115
|
+
node-version: '22'
|
|
116
|
+
|
|
117
|
+
- name: Cache promptfoo
|
|
118
|
+
uses: actions/cache@v4
|
|
119
|
+
with:
|
|
120
|
+
path: ~/.cache/promptfoo
|
|
121
|
+
key: ${{ runner.os }}-promptfoo-templates-${{ hashFiles('evals/promptfooconfig.templates.yaml') }}
|
|
122
|
+
|
|
123
|
+
- name: Run template evals
|
|
124
|
+
env:
|
|
125
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
126
|
+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
127
|
+
PROMPTFOO_CACHE_PATH: ~/.cache/promptfoo
|
|
128
|
+
working-directory: evals
|
|
129
|
+
run: |
|
|
130
|
+
npx promptfoo@latest eval \
|
|
131
|
+
-c promptfooconfig.templates.yaml \
|
|
132
|
+
-o results-templates.json \
|
|
133
|
+
-o report-templates.html
|
|
134
|
+
|
|
135
|
+
- name: Check quality gate (templates)
|
|
136
|
+
working-directory: evals
|
|
137
|
+
run: |
|
|
138
|
+
FAILURES=$(jq '.results.stats.failures' results-templates.json)
|
|
139
|
+
TOTAL=$(jq '.results.stats.successes + .results.stats.failures' results-templates.json)
|
|
140
|
+
echo "Template eval: $((TOTAL - FAILURES))/$TOTAL passed"
|
|
141
|
+
if [ "$FAILURES" -gt 0 ]; then
|
|
142
|
+
echo "::warning::Template eval had $FAILURES failures — review report"
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
- name: Upload template results
|
|
146
|
+
if: always()
|
|
147
|
+
uses: actions/upload-artifact@v4
|
|
148
|
+
with:
|
|
149
|
+
name: eval-results-templates
|
|
150
|
+
path: |
|
|
151
|
+
evals/results-templates.json
|
|
152
|
+
evals/report-templates.html
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v[0-9]+.[0-9]+.[0-9]+" # v0.1.0, v1.2.3
|
|
7
|
+
- "v[0-9]+.[0-9]+.[0-9]+a[0-9]+" # v0.1.0a1 (alpha)
|
|
8
|
+
- "v[0-9]+.[0-9]+.[0-9]+rc[0-9]+" # v0.1.0rc1 (release candidate)
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: publish-${{ github.ref }}
|
|
15
|
+
cancel-in-progress: false # Never cancel an in-flight publish
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
# Build all packages
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
build:
|
|
22
|
+
name: Build packages
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
|
|
28
|
+
- uses: actions/setup-python@v5
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.11"
|
|
31
|
+
|
|
32
|
+
- name: Install Hatch
|
|
33
|
+
run: pip install hatch>=1.7
|
|
34
|
+
|
|
35
|
+
- name: Build all workspace packages
|
|
36
|
+
run: |
|
|
37
|
+
for pkg in packages/core packages/stac packages/raster packages/vector packages/cli packages/cube packages/pipeline; do
|
|
38
|
+
echo "── Building $pkg ──"
|
|
39
|
+
(cd "$pkg" && hatch build --clean -t wheel -t sdist)
|
|
40
|
+
done
|
|
41
|
+
|
|
42
|
+
# Build the root earthforge meta-package (pip install earthforge[all])
|
|
43
|
+
echo "── Building root meta-package ──"
|
|
44
|
+
hatch build --clean -t wheel -t sdist
|
|
45
|
+
|
|
46
|
+
- name: Collect dist artifacts
|
|
47
|
+
run: |
|
|
48
|
+
mkdir -p dist_all
|
|
49
|
+
find packages/*/dist \( -name "*.whl" -o -name "*.tar.gz" \) -exec cp {} dist_all/ \;
|
|
50
|
+
cp dist/*.whl dist/*.tar.gz dist_all/ 2>/dev/null || true
|
|
51
|
+
ls -lh dist_all/
|
|
52
|
+
|
|
53
|
+
- uses: actions/upload-artifact@v4
|
|
54
|
+
with:
|
|
55
|
+
name: dist-packages
|
|
56
|
+
path: dist_all/
|
|
57
|
+
retention-days: 7
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# Verify packages installable before publishing
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
verify:
|
|
63
|
+
name: Verify packages
|
|
64
|
+
needs: build
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
|
|
67
|
+
steps:
|
|
68
|
+
- uses: actions/download-artifact@v4
|
|
69
|
+
with:
|
|
70
|
+
name: dist-packages
|
|
71
|
+
path: dist_all/
|
|
72
|
+
|
|
73
|
+
- uses: actions/setup-python@v5
|
|
74
|
+
with:
|
|
75
|
+
python-version: "3.11"
|
|
76
|
+
|
|
77
|
+
- name: Install and smoke-test each wheel
|
|
78
|
+
run: |
|
|
79
|
+
for whl in dist_all/*.whl; do
|
|
80
|
+
echo "Testing $whl"
|
|
81
|
+
pip install "$whl" --quiet || echo "WARN: $whl install failed (may need extras)"
|
|
82
|
+
done
|
|
83
|
+
python -c "from earthforge.core import __version__; print('Version:', __version__)"
|
|
84
|
+
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
# Publish to PyPI via API token stored in repo secrets
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
publish:
|
|
89
|
+
name: Publish to PyPI
|
|
90
|
+
needs: [build, verify]
|
|
91
|
+
runs-on: ubuntu-latest
|
|
92
|
+
environment:
|
|
93
|
+
name: pypi
|
|
94
|
+
url: https://pypi.org/project/earthforge/
|
|
95
|
+
|
|
96
|
+
steps:
|
|
97
|
+
- uses: actions/download-artifact@v4
|
|
98
|
+
with:
|
|
99
|
+
name: dist-packages
|
|
100
|
+
path: dist_all/
|
|
101
|
+
|
|
102
|
+
- name: Publish to PyPI
|
|
103
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
104
|
+
with:
|
|
105
|
+
packages-dir: dist_all/
|
|
106
|
+
password: ${{ secrets.PIPY_API_TOKEN }}
|
|
107
|
+
skip-existing: true
|
|
108
|
+
verbose: true
|
|
109
|
+
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
# Create GitHub Release with changelog excerpt
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
release:
|
|
114
|
+
name: Create GitHub Release
|
|
115
|
+
needs: publish
|
|
116
|
+
runs-on: ubuntu-latest
|
|
117
|
+
permissions:
|
|
118
|
+
contents: write
|
|
119
|
+
|
|
120
|
+
steps:
|
|
121
|
+
- uses: actions/checkout@v4
|
|
122
|
+
|
|
123
|
+
- name: Extract changelog for this version
|
|
124
|
+
id: changelog
|
|
125
|
+
run: |
|
|
126
|
+
VERSION="${GITHUB_REF_NAME#v}"
|
|
127
|
+
# Extract the section for this version from CHANGELOG.md
|
|
128
|
+
NOTES=$(awk "/^## \[$VERSION\]/{flag=1; next} /^## \[/{flag=0} flag" CHANGELOG.md)
|
|
129
|
+
if [ -z "$NOTES" ]; then
|
|
130
|
+
NOTES="See [CHANGELOG.md](CHANGELOG.md) for release notes."
|
|
131
|
+
fi
|
|
132
|
+
echo "notes<<EOF" >> "$GITHUB_OUTPUT"
|
|
133
|
+
echo "$NOTES" >> "$GITHUB_OUTPUT"
|
|
134
|
+
echo "EOF" >> "$GITHUB_OUTPUT"
|
|
135
|
+
|
|
136
|
+
- uses: actions/download-artifact@v4
|
|
137
|
+
with:
|
|
138
|
+
name: dist-packages
|
|
139
|
+
path: dist_all/
|
|
140
|
+
|
|
141
|
+
- name: Create GitHub Release
|
|
142
|
+
uses: softprops/action-gh-release@v2
|
|
143
|
+
with:
|
|
144
|
+
tag_name: ${{ github.ref_name }}
|
|
145
|
+
name: EarthForge ${{ github.ref_name }}
|
|
146
|
+
body: ${{ steps.changelog.outputs.notes }}
|
|
147
|
+
files: dist_all/*
|
|
148
|
+
draft: false
|
|
149
|
+
prerelease: ${{ contains(github.ref_name, 'a') || contains(github.ref_name, 'rc') }}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
*.egg
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
*.whl
|
|
9
|
+
|
|
10
|
+
# Virtual environments
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
|
|
14
|
+
# IDE
|
|
15
|
+
.vscode/
|
|
16
|
+
.idea/
|
|
17
|
+
*.swp
|
|
18
|
+
*.swo
|
|
19
|
+
|
|
20
|
+
# Testing
|
|
21
|
+
.pytest_cache/
|
|
22
|
+
.coverage
|
|
23
|
+
htmlcov/
|
|
24
|
+
.mypy_cache/
|
|
25
|
+
|
|
26
|
+
# OS
|
|
27
|
+
.DS_Store
|
|
28
|
+
Thumbs.db
|
|
29
|
+
|
|
30
|
+
# Claude
|
|
31
|
+
.claude/
|