curupira 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 (92) hide show
  1. curupira-0.1.0/.gitignore +190 -0
  2. curupira-0.1.0/CHANGELOG.md +48 -0
  3. curupira-0.1.0/CONTRIBUTING.md +221 -0
  4. curupira-0.1.0/LICENSE +21 -0
  5. curupira-0.1.0/PKG-INFO +324 -0
  6. curupira-0.1.0/README.md +290 -0
  7. curupira-0.1.0/SECURITY.md +27 -0
  8. curupira-0.1.0/crates/curupira-core/Cargo.lock +132 -0
  9. curupira-0.1.0/crates/curupira-core/Cargo.toml +14 -0
  10. curupira-0.1.0/crates/curupira-core/src/lib.rs +15 -0
  11. curupira-0.1.0/curupira.example.toml +92 -0
  12. curupira-0.1.0/hatch_build.py +100 -0
  13. curupira-0.1.0/pyproject.toml +142 -0
  14. curupira-0.1.0/src/curupira/__init__.py +5 -0
  15. curupira-0.1.0/src/curupira/__main__.py +5 -0
  16. curupira-0.1.0/src/curupira/_native.pyi +4 -0
  17. curupira-0.1.0/src/curupira/_version.py +3 -0
  18. curupira-0.1.0/src/curupira/agents/__init__.py +34 -0
  19. curupira-0.1.0/src/curupira/agents/base.py +112 -0
  20. curupira-0.1.0/src/curupira/agents/claude.py +30 -0
  21. curupira-0.1.0/src/curupira/agents/codex.py +40 -0
  22. curupira-0.1.0/src/curupira/agents/cursor.py +29 -0
  23. curupira-0.1.0/src/curupira/agents/opencode.py +30 -0
  24. curupira-0.1.0/src/curupira/cli.py +260 -0
  25. curupira-0.1.0/src/curupira/clients/__init__.py +1 -0
  26. curupira-0.1.0/src/curupira/clients/gh.py +198 -0
  27. curupira-0.1.0/src/curupira/clients/process.py +133 -0
  28. curupira-0.1.0/src/curupira/config.py +114 -0
  29. curupira-0.1.0/src/curupira/dispatcher.py +69 -0
  30. curupira-0.1.0/src/curupira/errors.py +68 -0
  31. curupira-0.1.0/src/curupira/executor.py +181 -0
  32. curupira-0.1.0/src/curupira/models/__init__.py +71 -0
  33. curupira-0.1.0/src/curupira/models/base.py +15 -0
  34. curupira-0.1.0/src/curupira/models/configuration.py +285 -0
  35. curupira-0.1.0/src/curupira/models/github.py +47 -0
  36. curupira-0.1.0/src/curupira/models/process.py +47 -0
  37. curupira-0.1.0/src/curupira/models/profiles.py +107 -0
  38. curupira-0.1.0/src/curupira/models/tasks.py +87 -0
  39. curupira-0.1.0/src/curupira/native.py +15 -0
  40. curupira-0.1.0/src/curupira/py.typed +0 -0
  41. curupira-0.1.0/src/curupira/runtime.py +117 -0
  42. curupira-0.1.0/src/curupira/scheduler.py +156 -0
  43. curupira-0.1.0/src/curupira/status.py +53 -0
  44. curupira-0.1.0/src/curupira/storage/__init__.py +6 -0
  45. curupira-0.1.0/src/curupira/storage/cron.py +119 -0
  46. curupira-0.1.0/src/curupira/storage/key_value.py +203 -0
  47. curupira-0.1.0/src/curupira/storage/sessions.py +29 -0
  48. curupira-0.1.0/src/curupira/tasks/__init__.py +5 -0
  49. curupira-0.1.0/src/curupira/tasks/base.py +62 -0
  50. curupira-0.1.0/src/curupira/tasks/cron.py +144 -0
  51. curupira-0.1.0/src/curupira/tasks/feed.py +103 -0
  52. curupira-0.1.0/src/curupira/tasks/github_issues.py +79 -0
  53. curupira-0.1.0/src/curupira/tasks/github_pull_requests.py +100 -0
  54. curupira-0.1.0/src/curupira/tasks/registry.py +33 -0
  55. curupira-0.1.0/src/curupira/telemetry.py +83 -0
  56. curupira-0.1.0/src/curupira/vcs/__init__.py +5 -0
  57. curupira-0.1.0/src/curupira/vcs/base.py +200 -0
  58. curupira-0.1.0/src/curupira/vcs/github_cli.py +19 -0
  59. curupira-0.1.0/src/gh_dispatch/tasks/__init__.py +1 -0
  60. curupira-0.1.0/tests/__init__.py +1 -0
  61. curupira-0.1.0/tests/agents/__init__.py +1 -0
  62. curupira-0.1.0/tests/agents/test_base.py +95 -0
  63. curupira-0.1.0/tests/agents/test_claude.py +70 -0
  64. curupira-0.1.0/tests/agents/test_codex.py +60 -0
  65. curupira-0.1.0/tests/agents/test_cursor.py +93 -0
  66. curupira-0.1.0/tests/agents/test_opencode.py +46 -0
  67. curupira-0.1.0/tests/fakes.py +137 -0
  68. curupira-0.1.0/tests/helpers.py +55 -0
  69. curupira-0.1.0/tests/tasks/__init__.py +1 -0
  70. curupira-0.1.0/tests/tasks/test_base.py +66 -0
  71. curupira-0.1.0/tests/tasks/test_cron.py +91 -0
  72. curupira-0.1.0/tests/tasks/test_cron_trigger.py +34 -0
  73. curupira-0.1.0/tests/tasks/test_feed.py +122 -0
  74. curupira-0.1.0/tests/tasks/test_github_issues.py +87 -0
  75. curupira-0.1.0/tests/tasks/test_github_pull_requests.py +106 -0
  76. curupira-0.1.0/tests/tasks/test_registry.py +79 -0
  77. curupira-0.1.0/tests/test_cli.py +157 -0
  78. curupira-0.1.0/tests/test_clients.py +303 -0
  79. curupira-0.1.0/tests/test_config.py +277 -0
  80. curupira-0.1.0/tests/test_dispatcher.py +299 -0
  81. curupira-0.1.0/tests/test_native.py +17 -0
  82. curupira-0.1.0/tests/test_process.py +123 -0
  83. curupira-0.1.0/tests/test_runtime.py +76 -0
  84. curupira-0.1.0/tests/test_scheduler.py +118 -0
  85. curupira-0.1.0/tests/test_session_repository.py +61 -0
  86. curupira-0.1.0/tests/test_status.py +57 -0
  87. curupira-0.1.0/tests/test_tasks.py +69 -0
  88. curupira-0.1.0/tests/test_telemetry.py +179 -0
  89. curupira-0.1.0/tests/test_verify_dist_version.py +112 -0
  90. curupira-0.1.0/tests/vcs/__init__.py +1 -0
  91. curupira-0.1.0/tests/vcs/test_base.py +150 -0
  92. curupira-0.1.0/uv.lock +2243 -0
@@ -0,0 +1,190 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Local curupira configuration and runtime state
40
+ /*-dispatch.toml
41
+ *.sqlite3
42
+ *.sqlite3-journal
43
+ workspaces/
44
+
45
+ # Unit test / coverage reports
46
+ htmlcov/
47
+ .tox/
48
+ .nox/
49
+ .coverage
50
+ .coverage.*
51
+ .cache
52
+ nosetests.xml
53
+ coverage.xml
54
+ *.cover
55
+ *.py,cover
56
+ .hypothesis/
57
+ .pytest_cache/
58
+ cover/
59
+
60
+ # Translations
61
+ *.mo
62
+ *.pot
63
+
64
+ # Django stuff:
65
+ *.log
66
+ local_settings.py
67
+ db.sqlite3
68
+ db.sqlite3-journal
69
+
70
+ # Flask stuff:
71
+ instance/
72
+ .webassets-cache
73
+
74
+ # Scrapy stuff:
75
+ .scrapy
76
+
77
+ # Sphinx documentation
78
+ docs/_build/
79
+
80
+ # PyBuilder
81
+ .pybuilder/
82
+ target/
83
+
84
+ # Jupyter Notebook
85
+ .ipynb_checkpoints
86
+
87
+ # IPython
88
+ profile_default/
89
+ ipython_config.py
90
+
91
+ # pyenv
92
+ # For a library or package, you might want to ignore these files since the code is
93
+ # intended to run in multiple environments; otherwise, check them in:
94
+ # .python-version
95
+
96
+ # pipenv
97
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
98
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
99
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
100
+ # install all needed dependencies.
101
+ #Pipfile.lock
102
+
103
+ # UV
104
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ #uv.lock
108
+
109
+ # poetry
110
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
111
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
112
+ # commonly ignored for libraries.
113
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
114
+ #poetry.lock
115
+
116
+ # pdm
117
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
118
+ #pdm.lock
119
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
120
+ # in version control.
121
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
122
+ .pdm.toml
123
+ .pdm-python
124
+ .pdm-build/
125
+
126
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
127
+ __pypackages__/
128
+
129
+ # Celery stuff
130
+ celerybeat-schedule
131
+ celerybeat.pid
132
+
133
+ # SageMath parsed files
134
+ *.sage.py
135
+
136
+ # Environments
137
+ .env
138
+ .venv
139
+ env/
140
+ venv/
141
+ ENV/
142
+ env.bak/
143
+ venv.bak/
144
+
145
+ # Spyder project settings
146
+ .spyderproject
147
+ .spyproject
148
+
149
+ # Rope project settings
150
+ .ropeproject
151
+
152
+ # mkdocs documentation
153
+ /site
154
+
155
+ # mypy
156
+ .mypy_cache/
157
+ .dmypy.json
158
+ dmypy.json
159
+
160
+ # Pyre type checker
161
+ .pyre/
162
+
163
+ # pytype static type analyzer
164
+ .pytype/
165
+
166
+ # Cython debug symbols
167
+ cython_debug/
168
+
169
+ # PyCharm
170
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
171
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
172
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
173
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
174
+ #.idea/
175
+
176
+ # Ruff stuff:
177
+ .ruff_cache/
178
+
179
+ # Pyrefly stuff:
180
+ .pyrefly/
181
+
182
+ # PyPI configuration file
183
+ .pypirc
184
+
185
+ # Cursor
186
+ # Cursor is an AI-powered code editor.`.cursorignore` specifies files/directories to
187
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
188
+ # refer to https://docs.cursor.com/context/ignore-files
189
+ .cursorignore
190
+ .cursorindexingignore
@@ -0,0 +1,48 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+
6
+ ## [0.1.0] - 2026-10-07
7
+
8
+ ### Added
9
+
10
+ - Rust crate `crates/curupira-core`, compiled with maturin into the `curupira._native`
11
+ extension shipped in the wheel. `rust_core_version()` returns the crate version.
12
+ - Keyed automations with a `trigger_type` discriminator (`issue`, `pull_request`,
13
+ `cron`) sharing one discovery, scheduling, and execution pipeline.
14
+ - Native provider adapters for OpenCode, Codex, Claude Code, and Cursor with
15
+ optional `model`/`effort`/`agent` translation and explicit permission options.
16
+ - Bounded async process runner with disconnected stdin, output limits, timeouts,
17
+ and process-group cleanup.
18
+ - Non-destructive SQLite state: incompatible files raise instead of being deleted.
19
+ - `validate`, `run` (with `--dry-run`), and `watch` commands with documented exit
20
+ codes.
21
+ - Local dispatch for GitHub issues, pull requests, and cron tasks, with isolated
22
+ task worktrees and persistent SQLite scheduling state.
23
+ - User and contributor guides at <https://caipora-labs.github.io/curupira/>.
24
+
25
+ ### Changed
26
+
27
+ - Development tags `vX.Y.Z.devN` publish that PEP 440 version to PyPI through
28
+ `publish.yml` (environment `pypi`) and do not open a GitHub Release. Stable
29
+ `vX.Y.Z` tags publish to PyPI and open a GitHub Release.
30
+ - Renamed the installable package, primary console script, and Python import from `curupi`
31
+ to `curupira`, and added the short CLI alias `curu`. The product name remains OpsCli.
32
+ Configuration and state now default to `~/.curupira` (example file
33
+ `curupira.example.toml`, log `logs/curupira.log`). Task worktree branches use the
34
+ `curupira/` prefix. The native crate is `crates/curupira-core`, imported as
35
+ `curupira._native`.
36
+ - Renamed the installable package, console script, and Python import from `opscli`
37
+ to `curupi`. The product name remains OpsCli. Configuration and state now default
38
+ to `~/.curupi` (example file `curupi.example.toml`, log `logs/curupi.log`). Task
39
+ worktree branches use the `curupi/` prefix. The native crate is `crates/curupi-core`,
40
+ imported as `curupi._native`.
41
+ - Renamed the product to OpsCli, the package and executable to `opscli`, and the
42
+ per-user runtime directory to `~/.opscli`.
43
+ - Configuration moved to `settings` plus `coding_agents` with global polling and
44
+ per-automation prompts; the map key is the automation ID.
45
+ - Custom-agent names are only accepted where a verified native flag exists
46
+ (`--agent` for OpenCode and Claude Code).
47
+
48
+ ## [Unreleased]
@@ -0,0 +1,221 @@
1
+ # Contributing
2
+
3
+ **OpsCli** (by Caipora Labs) is the product name. The installable package, primary console script, and Python import are `curupira`. The short CLI alias is `curu`.
4
+
5
+ ## Environment setup
6
+
7
+ Requirements: Python 3.11+, [`uv`](https://docs.astral.sh/uv/), `gh` for live
8
+ discovery (tests use fakes and need no authentication), and a stable [Rust](https://rustup.rs/)
9
+ toolchain when building the native extension or a wheel.
10
+
11
+ ```bash
12
+ uv sync --dev
13
+ ```
14
+
15
+ `uv sync` installs the Python package and the `curupira` and `curu` console scripts. It does not
16
+ compile Rust, so the CLI and the Python test suite run without a toolchain.
17
+
18
+ A fresh contributor verifies everything with the commands below. They must all pass
19
+ before opening a pull request; CI runs the same steps. `uv build` compiles the native
20
+ extension and therefore needs Rust on `PATH`.
21
+
22
+ ## Verification commands
23
+
24
+ Install the documentation tools with `uv sync --group docs`, then preview the site with
25
+ `uv run mkdocs serve`.
26
+
27
+ ```bash
28
+ uv run --no-sync pytest
29
+ uv run --no-sync ruff check .
30
+ uv run --no-sync ruff format --check .
31
+ uv run --no-sync pyrefly check
32
+ uv build
33
+ uv run --no-sync twine check dist/*
34
+ ```
35
+
36
+ ## Native extension
37
+
38
+ The Rust crate is `crates/curupira-core`, matching the `curupira` distribution name.
39
+ Hatchling stays the PEP 517 backend, keeps the version in `src/curupira/_version.py`,
40
+ and keeps the `curupira` and `curu` script entry points. A wheel build hook runs maturin (PyO3)
41
+ and packs the compiled module as `curupira._native`.
42
+ Editable installs skip that compile.
43
+
44
+ Build the extension into the current environment:
45
+
46
+ ```bash
47
+ uv sync --dev
48
+ uv run maturin develop
49
+ python -c "from curupira._native import rust_core_version; print(rust_core_version())"
50
+ ```
51
+
52
+ `maturin develop` warns that the build backend is Hatchling. That warning is expected:
53
+ Hatchling still packages the Python project and the `curupira` and `curu` scripts, and maturin only
54
+ compiles the extension.
55
+
56
+ `rust_core_version()` returns the `curupira-core` crate version. `curupira.native.rust_core_version`
57
+ is a thin wrapper around that function. `tests/test_native.py` runs the same check when
58
+ the extension is already built and skips otherwise.
59
+
60
+ Build a wheel that includes the extension (this is what CI and `pip install` use):
61
+
62
+ ```bash
63
+ uv build
64
+ ```
65
+
66
+ When collecting coverage, it must stay at or above 85% branch coverage
67
+ (`fail_under = 85` in `pyproject.toml`). The example configuration is covered by tests:
68
+ changes to `curupira.example.toml` must keep `test_example_configuration_is_valid` green.
69
+
70
+ ## Architecture boundaries
71
+
72
+ OpsCli separates task discovery, repository version control, and coding-agent CLI
73
+ invocation into three layers. Each layer owns a contract in its `base.py`:
74
+
75
+ - `src/curupira/tasks/` discovers work. `tasks/base.py` defines `TaskFeed` (polling
76
+ and streaming discovered tasks), `TaskSource` (discovering tasks for an automation),
77
+ `Trigger` (trigger-specific prompt data and feed construction), and
78
+ `FeedDependencies`. `tasks/feed.py` provides the reusable `PollingTaskFeed`, while
79
+ `tasks/registry.py` registers trigger types and aliases. Current sources/triggers
80
+ are implemented in `tasks/cron.py`, `tasks/github_issues.py`, and
81
+ `tasks/github_pull_requests.py`.
82
+ - `src/curupira/vcs/` prepares repositories. `vcs/base.py` defines `VersionControl`;
83
+ providers implement its `clone(repo, destination)` operation, while shared checkout,
84
+ worktree, and setup behavior stays in the base class. `vcs/github_cli.py` implements
85
+ cloning through the GitHub CLI.
86
+ - `src/curupira/agents/` invokes coding-agent CLIs. `agents/base.py` defines
87
+ `CodingAgentCliAdapter`; an adapter implements `build_arguments(request)` to map a
88
+ validated task request to that CLI's native arguments. The factory
89
+ `create_cli_adapter` in `agents/__init__.py` constructs supported adapters:
90
+ `opencode.py`, `codex.py`, `claude.py`, and `cursor.py`.
91
+
92
+ `src/curupira/storage/` is local SQLite persistence, not a version-control provider.
93
+ OpsCli does not manage authentication: provider CLIs and the user's environment provide
94
+ their own authentication.
95
+
96
+ To add a task source, implement `TaskSource`, provide a `Trigger`, and register its
97
+ `trigger_type` in `tasks/registry.py`; use a dedicated issue/PR after the task layer's
98
+ `base.py` contract. A new version-control provider implements `VersionControl.clone`
99
+ and belongs in its own issue/PR after `vcs/base.py`. A new coding-agent adapter
100
+ implements `CodingAgentCliAdapter.build_arguments` and is wired into
101
+ `create_cli_adapter`; it belongs in its own issue/PR after `agents/base.py`. Trello,
102
+ Azure DevOps, and Monday are examples of services where a future task source could
103
+ belong; they are not currently supported providers. Configuration accepts only the
104
+ trigger types and agent profiles defined by the current registry and models.
105
+
106
+ Cover new behavior with fakes in `tests/` — never start authenticated agents or hit the
107
+ network in tests.
108
+
109
+ When adding a provider or CLI, update `docs/data/requirements.toml` and the corresponding
110
+ adapter in `src/curupira/agents/`; the English installation requirements are rendered from
111
+ that TOML file during the MkDocs build.
112
+
113
+ ## Documentation translations
114
+
115
+ Edit the canonical English pages in `docs/en/` first. When English documentation changes,
116
+ open a follow-up pull request to synchronize the corresponding Portuguese (`docs/pt/`) and
117
+ Spanish (`docs/es/`) pages. Generated Pydantic reference stays canonical in English; other
118
+ languages should link to it rather than manually translating generated fields.
119
+
120
+ ## Documentation brand tokens
121
+
122
+ The docs theme uses the Caipora Labs palette. These hex values are the brand tokens.
123
+ Do not add other brand colors without a new decision. The Material overrides live in
124
+ `docs/stylesheets/extra.css`.
125
+
126
+ | Token | Hex | Role |
127
+ | --- | --- | --- |
128
+ | `primary` | `#F7931F` | orange brand |
129
+ | `primary-deep` | `#EA6114` | contrast / CTAs |
130
+ | `skin` | `#8E4F26` | Caipora brown |
131
+ | `accent` | `#39873B` | leaf / success |
132
+ | `neutral-0` | `#FEFDFC` | background |
133
+ | `neutral-900` | `#1A1A1A` | text |
134
+
135
+ On the light scheme, the header uses `primary` with `neutral-900` text, and primary
136
+ buttons use `primary-deep`. Body links use `skin`, which stays readable on `neutral-0`.
137
+ The leaf `accent` is the hover color. The dark scheme swaps the neutrals and uses
138
+ `primary` for links. The optional OpsCli product accent (`#014FC9` / `#011E58`) is not
139
+ applied on the docs theme.
140
+
141
+ ## Dependency audits
142
+
143
+ `pip-audit` runs in CI against the synced development environment:
144
+
145
+ ```bash
146
+ uv run pip-audit
147
+ ```
148
+
149
+ Investigate every finding: upgrade the affected constraint in `pyproject.toml`,
150
+ re-sync the lockfile, and re-run the full verification suite. If a finding is not
151
+ exploitable in this project (for example, a dev-only tool with no network path to
152
+ untrusted input), document the reason in the pull request instead of adding a
153
+ permanent ignore.
154
+
155
+ ## Releases
156
+
157
+ Versioning is `MAJOR.MINOR.PATCH`. The single version source is
158
+ `src/curupira/_version.py`; the build backend reads it, and the CLI reports it.
159
+ Built wheels and sdists use that string as-is, so the Git tag and the file match
160
+ character for character after the tag's leading `v`.
161
+
162
+ ### PyPI development rehearsal
163
+
164
+ Tag `vX.Y.Z.devN` publishes package `X.Y.Z.devN` (PEP 440) to PyPI. Bump the version
165
+ in `src/curupira/_version.py`, commit that change, and tag the same commit. Tag
166
+ `v0.1.0.dev0` already exists and must not be reused. The next rehearsal is
167
+ `0.1.0.dev1` with tag `v0.1.0.dev1`.
168
+
169
+ 1. Set `__version__` to the next unused `X.Y.Z.devN` and commit.
170
+ 2. Wait until CI is green on that commit. The tag workflow publishes only after the
171
+ Linux test matrix, lint, type check, and distribution build have succeeded for
172
+ the tagged SHA.
173
+ 3. Tag that commit and push the tag:
174
+
175
+ ```bash
176
+ git tag v0.1.0.dev1
177
+ git push origin v0.1.0.dev1
178
+ ```
179
+
180
+ `publish.yml` builds the Rust-enabled wheel and sdist with `uv build`, checks that
181
+ the distribution version equals the tag without its leading `v`, and publishes with
182
+ Trusted Publishing (`id-token: write`, environment `pypi`). Use the canonical dotted
183
+ form `vX.Y.Z.devN`. PyPI keeps an uploaded file, so each rehearsal needs a new
184
+ suffix.
185
+
186
+ Tags that contain `.dev` do not open a GitHub Release (`release.yml` still skips
187
+ them). `testpypi.yml` is unchanged: the same `v*.dev*` tags, and a manual
188
+ `workflow_dispatch` from `main`, still target TestPyPI. The rehearsal that gates the
189
+ first stable publish is the real PyPI upload from `publish.yml`.
190
+
191
+ Stable `vX.Y.Z` tags keep the production path below. Issue #103 publishes `0.1.0`
192
+ from a commit whose `__version__` is `0.1.0`.
193
+
194
+ To cut a release:
195
+
196
+ 1. Move the `Unreleased` entries in `CHANGELOG.md` into a new version section.
197
+ 2. Bump `__version__` in `src/curupira/_version.py` to the stable `X.Y.Z` version.
198
+ 3. Run the full verification suite and confirm `uv build` plus
199
+ `uv run --no-sync twine check dist/*` pass.
200
+ 4. Rehearse with a `vX.Y.Z.devN` tag on PyPI (see above) before the first production
201
+ publication.
202
+ 5. Tag the validated commit as `vX.Y.Z` and push the tag. The `publish.yml` workflow
203
+ publishes that version to PyPI; the `release.yml` workflow attaches the distributions
204
+ to the matching GitHub release.
205
+
206
+ PyPI publishing uses Trusted Publishing (OIDC), so no API tokens are stored. Before
207
+ publishing, a PyPI maintainer registers this repository as a trusted publisher for the
208
+ `curupira` project with GitHub owner `caipora-labs`, repository `curupira`, workflow
209
+ filename `publish.yml`, and environment `pypi`. Development tags `vX.Y.Z.devN` use
210
+ that same publisher. `testpypi.yml` remains a separate workflow with environment
211
+ `testpypi`, workflow filename `testpypi.yml`, and audience `testpypi`. It uploads to
212
+ `https://test.pypi.org/legacy/` and smoke-tests that installation. The publish
213
+ workflow checks that the full Linux test matrix, lint, type check, and distribution
214
+ build succeeded for the commit.
215
+
216
+ ## Style
217
+
218
+ Ruff (lint + format) and strict Pyrefly govern style: typed signatures everywhere,
219
+ `@override` on overrides, docstrings on public modules/classes/methods, no blanket
220
+ suppressions. Test helpers may use `**overrides: Any`; keep other `Any` usage narrow
221
+ and justified.
curupira-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mario Taddeucci
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.