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.
Files changed (179) hide show
  1. earthforge-0.1.0/.github/copilot-instructions.md +72 -0
  2. earthforge-0.1.0/.github/workflows/ci.yml +189 -0
  3. earthforge-0.1.0/.github/workflows/docs.yml +52 -0
  4. earthforge-0.1.0/.github/workflows/prompt-eval.yml +152 -0
  5. earthforge-0.1.0/.github/workflows/publish.yml +149 -0
  6. earthforge-0.1.0/.gitignore +31 -0
  7. earthforge-0.1.0/=1.7 +145 -0
  8. earthforge-0.1.0/ARCHITECTURE.md +88 -0
  9. earthforge-0.1.0/CHANGELOG.md +105 -0
  10. earthforge-0.1.0/CLAUDE.md +196 -0
  11. earthforge-0.1.0/CONTRIBUTING.md +130 -0
  12. earthforge-0.1.0/LICENSE +674 -0
  13. earthforge-0.1.0/PKG-INFO +273 -0
  14. earthforge-0.1.0/README.md +227 -0
  15. earthforge-0.1.0/ai-dev/agents/README.md +25 -0
  16. earthforge-0.1.0/ai-dev/agents/architect.md +48 -0
  17. earthforge-0.1.0/ai-dev/agents/gis_domain_expert.md +97 -0
  18. earthforge-0.1.0/ai-dev/agents/python_expert.md +232 -0
  19. earthforge-0.1.0/ai-dev/architecture.md +343 -0
  20. earthforge-0.1.0/ai-dev/decisions/DL-001-monorepo.md +34 -0
  21. earthforge-0.1.0/ai-dev/decisions/DL-002-async-first-io.md +31 -0
  22. earthforge-0.1.0/ai-dev/decisions/DL-003-storage-abstraction.md +27 -0
  23. earthforge-0.1.0/ai-dev/decisions/DL-004-output-contract.md +74 -0
  24. earthforge-0.1.0/ai-dev/decisions/DL-005-rust-boundary.md +48 -0
  25. earthforge-0.1.0/ai-dev/decisions/DL-006-engineering-credibility.md +52 -0
  26. earthforge-0.1.0/ai-dev/decisions/DL-007-promptfoo-eval.md +36 -0
  27. earthforge-0.1.0/ai-dev/guardrails/README.md +11 -0
  28. earthforge-0.1.0/ai-dev/guardrails/cloud-native-compliance.md +37 -0
  29. earthforge-0.1.0/ai-dev/guardrails/coding-standards.md +64 -0
  30. earthforge-0.1.0/ai-dev/guardrails/data-handling.md +21 -0
  31. earthforge-0.1.0/ai-dev/patterns.md +69 -0
  32. earthforge-0.1.0/ai-dev/prompt-templates.md +71 -0
  33. earthforge-0.1.0/ai-dev/skills/README.md +15 -0
  34. earthforge-0.1.0/ai-dev/skills/cloud-native-formats.md +134 -0
  35. earthforge-0.1.0/ai-dev/spec.md +112 -0
  36. earthforge-0.1.0/ai-dev/test-data-plan.md +216 -0
  37. earthforge-0.1.0/ai-dev/validation-reports/README.md +91 -0
  38. earthforge-0.1.0/ai-dev/validation-reports/VR-M0-format-detection.md +57 -0
  39. earthforge-0.1.0/ai-dev/validation-reports/VR-M0-raster-info.md +40 -0
  40. earthforge-0.1.0/ai-dev/validation-reports/VR-M0-vector-info.md +42 -0
  41. earthforge-0.1.0/ai-dev/validation-reports/VR-M1-kygeonet-integration.md +81 -0
  42. earthforge-0.1.0/ai-dev/validation-reports/VR-M1-raster-preview.md +46 -0
  43. earthforge-0.1.0/ai-dev/validation-reports/VR-M1-raster-validate.md +43 -0
  44. earthforge-0.1.0/ai-dev/validation-reports/VR-M2-raster-convert.md +54 -0
  45. earthforge-0.1.0/ai-dev/validation-reports/VR-M2-vector-convert.md +47 -0
  46. earthforge-0.1.0/ai-dev/validation-reports/VR-M2-vector-query.md +52 -0
  47. earthforge-0.1.0/ai-dev/validation-reports/VR-M3-cube-info.md +90 -0
  48. earthforge-0.1.0/ai-dev/validation-reports/VR-M3-pipeline-run.md +115 -0
  49. earthforge-0.1.0/ai-dev/validation-reports/VR-M3-stac-fetch.md +65 -0
  50. earthforge-0.1.0/branding/earthforge-banner.png +0 -0
  51. earthforge-0.1.0/data/.gitignore +11 -0
  52. earthforge-0.1.0/data/samples/kyfromabove_preview.png +0 -0
  53. earthforge-0.1.0/data/samples/raster_info.json +27 -0
  54. earthforge-0.1.0/data/samples/stac_fetch.json +21 -0
  55. earthforge-0.1.0/data/samples/stac_search.json +39 -0
  56. earthforge-0.1.0/data/samples/vector_info.json +21 -0
  57. earthforge-0.1.0/docs/api/reference.md +85 -0
  58. earthforge-0.1.0/docs/architecture.md +126 -0
  59. earthforge-0.1.0/docs/cli.md +314 -0
  60. earthforge-0.1.0/docs/getting-started.md +155 -0
  61. earthforge-0.1.0/docs/index.md +118 -0
  62. earthforge-0.1.0/docs/release-notes.md +66 -0
  63. earthforge-0.1.0/docs/stac-collections.md +133 -0
  64. earthforge-0.1.0/docs/tutorials/arcgis-pro.md +167 -0
  65. earthforge-0.1.0/docs/tutorials/dem-hillshade.md +149 -0
  66. earthforge-0.1.0/docs/tutorials/lidar-access.md +136 -0
  67. earthforge-0.1.0/docs/tutorials/titiler-maps.md +144 -0
  68. earthforge-0.1.0/evals/README.md +60 -0
  69. earthforge-0.1.0/evals/assertions/check_async_pattern.js +23 -0
  70. earthforge-0.1.0/evals/assertions/check_error_handling.js +28 -0
  71. earthforge-0.1.0/evals/assertions/check_no_direct_imports.js +43 -0
  72. earthforge-0.1.0/evals/assertions/check_structured_return.js +24 -0
  73. earthforge-0.1.0/evals/promptfooconfig.redteam.yaml +172 -0
  74. earthforge-0.1.0/evals/promptfooconfig.templates.yaml +66 -0
  75. earthforge-0.1.0/evals/promptfooconfig.yaml +142 -0
  76. earthforge-0.1.0/evals/prompts/python_expert_implement.txt +54 -0
  77. earthforge-0.1.0/evals/prompts/redteam_system.txt +45 -0
  78. earthforge-0.1.0/evals/prompts/template_implement_feature.txt +11 -0
  79. earthforge-0.1.0/examples/notebooks/01_getting_started.ipynb +206 -0
  80. earthforge-0.1.0/examples/notebooks/02_stac_search.ipynb +316 -0
  81. earthforge-0.1.0/examples/notebooks/03_vector_operations.ipynb +289 -0
  82. earthforge-0.1.0/examples/scripts/cube_info_era5_demo.py +81 -0
  83. earthforge-0.1.0/examples/scripts/ky_wma_demo.py +238 -0
  84. earthforge-0.1.0/examples/scripts/kygeonet_stac_demo.py +145 -0
  85. earthforge-0.1.0/examples/scripts/ndvi_pipeline.yaml +55 -0
  86. earthforge-0.1.0/examples/scripts/stac_fetch_kyfromabove_demo.py +149 -0
  87. earthforge-0.1.0/examples/web/docker-compose.yml +44 -0
  88. earthforge-0.1.0/examples/web/titiler_map.html +303 -0
  89. earthforge-0.1.0/mkdocs.yml +94 -0
  90. earthforge-0.1.0/packages/cli/README.md +5 -0
  91. earthforge-0.1.0/packages/cli/pyproject.toml +23 -0
  92. earthforge-0.1.0/packages/cli/src/earthforge/cli/__init__.py +6 -0
  93. earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/__init__.py +5 -0
  94. earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/bench_cmd.py +215 -0
  95. earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/completions_cmd.py +92 -0
  96. earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/config_cmd.py +69 -0
  97. earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/cube_cmd.py +104 -0
  98. earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/explore_cmd.py +98 -0
  99. earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/info.py +136 -0
  100. earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/pipeline_cmd.py +107 -0
  101. earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/raster_cmd.py +121 -0
  102. earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/stac_cmd.py +163 -0
  103. earthforge-0.1.0/packages/cli/src/earthforge/cli/commands/vector_cmd.py +126 -0
  104. earthforge-0.1.0/packages/cli/src/earthforge/cli/main.py +181 -0
  105. earthforge-0.1.0/packages/cli/src/earthforge/cli/tui/__init__.py +7 -0
  106. earthforge-0.1.0/packages/cli/src/earthforge/cli/tui/app.py +368 -0
  107. earthforge-0.1.0/packages/cli/tests/test_explore_cmd.py +194 -0
  108. earthforge-0.1.0/packages/cli/tests/test_main.py +149 -0
  109. earthforge-0.1.0/packages/core/README.md +5 -0
  110. earthforge-0.1.0/packages/core/pyproject.toml +21 -0
  111. earthforge-0.1.0/packages/core/src/earthforge/core/__init__.py +8 -0
  112. earthforge-0.1.0/packages/core/src/earthforge/core/config.py +239 -0
  113. earthforge-0.1.0/packages/core/src/earthforge/core/errors.py +85 -0
  114. earthforge-0.1.0/packages/core/src/earthforge/core/formats.py +440 -0
  115. earthforge-0.1.0/packages/core/src/earthforge/core/http.py +205 -0
  116. earthforge-0.1.0/packages/core/src/earthforge/core/output.py +206 -0
  117. earthforge-0.1.0/packages/core/src/earthforge/core/storage.py +272 -0
  118. earthforge-0.1.0/packages/core/tests/test_config.py +185 -0
  119. earthforge-0.1.0/packages/core/tests/test_errors.py +90 -0
  120. earthforge-0.1.0/packages/core/tests/test_formats.py +260 -0
  121. earthforge-0.1.0/packages/core/tests/test_http.py +160 -0
  122. earthforge-0.1.0/packages/core/tests/test_output.py +203 -0
  123. earthforge-0.1.0/packages/core/tests/test_storage.py +147 -0
  124. earthforge-0.1.0/packages/cube/README.md +8 -0
  125. earthforge-0.1.0/packages/cube/pyproject.toml +21 -0
  126. earthforge-0.1.0/packages/cube/src/earthforge/cube/__init__.py +7 -0
  127. earthforge-0.1.0/packages/cube/src/earthforge/cube/errors.py +15 -0
  128. earthforge-0.1.0/packages/cube/src/earthforge/cube/info.py +415 -0
  129. earthforge-0.1.0/packages/cube/src/earthforge/cube/py.typed +0 -0
  130. earthforge-0.1.0/packages/cube/src/earthforge/cube/slice.py +344 -0
  131. earthforge-0.1.0/packages/cube/tests/conftest.py +15 -0
  132. earthforge-0.1.0/packages/cube/tests/test_cube_info.py +236 -0
  133. earthforge-0.1.0/packages/cube/tests/test_cube_slice.py +227 -0
  134. earthforge-0.1.0/packages/pipeline/README.md +10 -0
  135. earthforge-0.1.0/packages/pipeline/pyproject.toml +19 -0
  136. earthforge-0.1.0/packages/pipeline/src/earthforge/pipeline/__init__.py +29 -0
  137. earthforge-0.1.0/packages/pipeline/src/earthforge/pipeline/errors.py +28 -0
  138. earthforge-0.1.0/packages/pipeline/src/earthforge/pipeline/py.typed +0 -0
  139. earthforge-0.1.0/packages/pipeline/src/earthforge/pipeline/runner.py +356 -0
  140. earthforge-0.1.0/packages/pipeline/src/earthforge/pipeline/schema.py +119 -0
  141. earthforge-0.1.0/packages/pipeline/src/earthforge/pipeline/steps.py +479 -0
  142. earthforge-0.1.0/packages/pipeline/src/earthforge/pipeline/template.py +81 -0
  143. earthforge-0.1.0/packages/pipeline/tests/conftest.py +13 -0
  144. earthforge-0.1.0/packages/pipeline/tests/test_pipeline_runner.py +249 -0
  145. earthforge-0.1.0/packages/pipeline/tests/test_pipeline_schema.py +141 -0
  146. earthforge-0.1.0/packages/pipeline/tests/test_pipeline_steps.py +206 -0
  147. earthforge-0.1.0/packages/raster/README.md +5 -0
  148. earthforge-0.1.0/packages/raster/pyproject.toml +19 -0
  149. earthforge-0.1.0/packages/raster/src/earthforge/raster/__init__.py +7 -0
  150. earthforge-0.1.0/packages/raster/src/earthforge/raster/convert.py +261 -0
  151. earthforge-0.1.0/packages/raster/src/earthforge/raster/errors.py +29 -0
  152. earthforge-0.1.0/packages/raster/src/earthforge/raster/info.py +214 -0
  153. earthforge-0.1.0/packages/raster/src/earthforge/raster/preview.py +181 -0
  154. earthforge-0.1.0/packages/raster/src/earthforge/raster/validate.py +195 -0
  155. earthforge-0.1.0/packages/raster/tests/test_raster_convert.py +179 -0
  156. earthforge-0.1.0/packages/raster/tests/test_raster_info.py +141 -0
  157. earthforge-0.1.0/packages/raster/tests/test_raster_preview.py +130 -0
  158. earthforge-0.1.0/packages/raster/tests/test_raster_validate.py +168 -0
  159. earthforge-0.1.0/packages/stac/README.md +3 -0
  160. earthforge-0.1.0/packages/stac/pyproject.toml +19 -0
  161. earthforge-0.1.0/packages/stac/src/earthforge/stac/__init__.py +6 -0
  162. earthforge-0.1.0/packages/stac/src/earthforge/stac/errors.py +33 -0
  163. earthforge-0.1.0/packages/stac/src/earthforge/stac/fetch.py +328 -0
  164. earthforge-0.1.0/packages/stac/src/earthforge/stac/info.py +210 -0
  165. earthforge-0.1.0/packages/stac/src/earthforge/stac/search.py +256 -0
  166. earthforge-0.1.0/packages/stac/tests/test_stac_fetch.py +277 -0
  167. earthforge-0.1.0/packages/stac/tests/test_stac_info.py +210 -0
  168. earthforge-0.1.0/packages/stac/tests/test_stac_search.py +213 -0
  169. earthforge-0.1.0/packages/vector/README.md +3 -0
  170. earthforge-0.1.0/packages/vector/pyproject.toml +18 -0
  171. earthforge-0.1.0/packages/vector/src/earthforge/vector/__init__.py +6 -0
  172. earthforge-0.1.0/packages/vector/src/earthforge/vector/convert.py +356 -0
  173. earthforge-0.1.0/packages/vector/src/earthforge/vector/errors.py +21 -0
  174. earthforge-0.1.0/packages/vector/src/earthforge/vector/info.py +245 -0
  175. earthforge-0.1.0/packages/vector/src/earthforge/vector/query.py +384 -0
  176. earthforge-0.1.0/packages/vector/tests/test_vector_convert.py +226 -0
  177. earthforge-0.1.0/packages/vector/tests/test_vector_info.py +174 -0
  178. earthforge-0.1.0/packages/vector/tests/test_vector_query.py +176 -0
  179. 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/