generic-ml-wrapper 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 (180) hide show
  1. generic_ml_wrapper-0.1.0/.coveragerc +4 -0
  2. generic_ml_wrapper-0.1.0/.gitattributes +1 -0
  3. generic_ml_wrapper-0.1.0/.github/workflows/ci.yml +112 -0
  4. generic_ml_wrapper-0.1.0/.github/workflows/pr-hygiene.yml +47 -0
  5. generic_ml_wrapper-0.1.0/.github/workflows/release.yml +28 -0
  6. generic_ml_wrapper-0.1.0/.github/workflows/sonar.yml +34 -0
  7. generic_ml_wrapper-0.1.0/.gitignore +44 -0
  8. generic_ml_wrapper-0.1.0/.importlinter +40 -0
  9. generic_ml_wrapper-0.1.0/.pre-commit-config.yaml +57 -0
  10. generic_ml_wrapper-0.1.0/AGENTS.md +31 -0
  11. generic_ml_wrapper-0.1.0/CHANGELOG.md +45 -0
  12. generic_ml_wrapper-0.1.0/CODE_OF_CONDUCT.md +89 -0
  13. generic_ml_wrapper-0.1.0/CONTRIBUTING.md +94 -0
  14. generic_ml_wrapper-0.1.0/GOVERNANCE.md +95 -0
  15. generic_ml_wrapper-0.1.0/LICENSE +201 -0
  16. generic_ml_wrapper-0.1.0/NOTICE +8 -0
  17. generic_ml_wrapper-0.1.0/PKG-INFO +172 -0
  18. generic_ml_wrapper-0.1.0/README.md +136 -0
  19. generic_ml_wrapper-0.1.0/SECURITY.md +122 -0
  20. generic_ml_wrapper-0.1.0/VERSION +1 -0
  21. generic_ml_wrapper-0.1.0/docs/DESIGN.md +328 -0
  22. generic_ml_wrapper-0.1.0/docs/images/gmlw-favicon.svg +16 -0
  23. generic_ml_wrapper-0.1.0/docs/images/gmlw-lockup-dark.svg +11 -0
  24. generic_ml_wrapper-0.1.0/docs/images/gmlw-lockup.svg +11 -0
  25. generic_ml_wrapper-0.1.0/docs/images/gmlw-statusline.gif +0 -0
  26. generic_ml_wrapper-0.1.0/docs/images/gmlw-usage.gif +0 -0
  27. generic_ml_wrapper-0.1.0/docs/tapes/README.md +24 -0
  28. generic_ml_wrapper-0.1.0/docs/tapes/render.sh +49 -0
  29. generic_ml_wrapper-0.1.0/docs/tapes/seed.py +42 -0
  30. generic_ml_wrapper-0.1.0/docs/tapes/statusline.tape +23 -0
  31. generic_ml_wrapper-0.1.0/docs/tapes/usage.tape +24 -0
  32. generic_ml_wrapper-0.1.0/noxfile.py +136 -0
  33. generic_ml_wrapper-0.1.0/pyproject.toml +116 -0
  34. generic_ml_wrapper-0.1.0/pyrightconfig.json +17 -0
  35. generic_ml_wrapper-0.1.0/sonar-project.properties +14 -0
  36. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/__init__.py +13 -0
  37. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/__init__.py +3 -0
  38. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/inbound/__init__.py +3 -0
  39. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/inbound/cli/__init__.py +3 -0
  40. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/inbound/cli/app.py +369 -0
  41. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/inbound/cli/banner.py +30 -0
  42. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/__init__.py +3 -0
  43. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/bootstrap/__init__.py +3 -0
  44. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/bootstrap/filesystem_layout_seeder.py +104 -0
  45. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/caller/__init__.py +3 -0
  46. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/caller/claude_cli_caller.py +158 -0
  47. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/caller/codex_cli_caller.py +165 -0
  48. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/caller/context_file.py +44 -0
  49. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/caller/context_opening.py +27 -0
  50. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/caller/cursor_cli_caller.py +98 -0
  51. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/caller/default_provider.py +79 -0
  52. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/caller/loader.py +34 -0
  53. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/caller/status_line_config.py +142 -0
  54. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/caller/vibe_cli_caller.py +169 -0
  55. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/caller/vibe_config.py +128 -0
  56. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/credentials/__init__.py +3 -0
  57. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/credentials/filesystem_credentials_store.py +130 -0
  58. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/gateway/__init__.py +3 -0
  59. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/gateway/anthropic_sse.py +148 -0
  60. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/gateway/openai_chat.py +112 -0
  61. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/gateway/openai_responses.py +85 -0
  62. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/gateway/relay.py +402 -0
  63. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/interceptor/__init__.py +3 -0
  64. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/interceptor/compressor.py +107 -0
  65. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/interceptor/size_logger.py +32 -0
  66. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/status/__init__.py +3 -0
  67. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/status/claude_status_parser.py +76 -0
  68. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/status/cursor_status_parser.py +71 -0
  69. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/store/__init__.py +3 -0
  70. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/store/filesystem_transcript_store.py +65 -0
  71. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/store/ledger.py +104 -0
  72. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/store/sqlite_per_turn_store.py +68 -0
  73. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/store/sqlite_session_store.py +73 -0
  74. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/store/sqlite_usage_store.py +44 -0
  75. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/workflow/__init__.py +3 -0
  76. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/workflow/filesystem_workflow_source.py +165 -0
  77. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/workspace/__init__.py +3 -0
  78. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/adapter/outbound/workspace/local_workspace_inspector.py +75 -0
  79. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/__init__.py +3 -0
  80. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/domain/__init__.py +3 -0
  81. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/domain/model/__init__.py +3 -0
  82. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/domain/model/client_status.py +44 -0
  83. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/domain/model/identifiers.py +76 -0
  84. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/domain/model/run.py +35 -0
  85. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/domain/model/session.py +24 -0
  86. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/domain/model/turn_usage.py +62 -0
  87. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/domain/model/workspace.py +31 -0
  88. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/domain/service/__init__.py +3 -0
  89. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/domain/service/interceptor.py +30 -0
  90. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/domain/service/interceptor_chain.py +57 -0
  91. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/domain/service/rule_cleaner.py +35 -0
  92. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/domain/service/session_naming.py +30 -0
  93. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/domain/service/statusline_renderer.py +81 -0
  94. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/__init__.py +3 -0
  95. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/inbound/__init__.py +3 -0
  96. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/inbound/bootstrap.py +15 -0
  97. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/inbound/export_usage.py +109 -0
  98. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/inbound/list_jobs.py +33 -0
  99. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/inbound/list_sessions.py +36 -0
  100. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/inbound/list_workflows.py +19 -0
  101. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/inbound/new_workflow.py +48 -0
  102. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/inbound/render_statusline.py +24 -0
  103. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/inbound/set_credential.py +35 -0
  104. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/inbound/start_job.py +53 -0
  105. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/outbound/__init__.py +3 -0
  106. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/outbound/cli_caller.py +97 -0
  107. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/outbound/client_status.py +27 -0
  108. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/outbound/credentials_store.py +36 -0
  109. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/outbound/interceptor.py +25 -0
  110. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/outbound/layout_seeder.py +18 -0
  111. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/outbound/per_turn_metering.py +38 -0
  112. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/outbound/session_store.py +62 -0
  113. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/outbound/transcript.py +46 -0
  114. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/outbound/usage_store.py +32 -0
  115. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/outbound/workflow_source.py +58 -0
  116. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/port/outbound/workspace.py +27 -0
  117. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/usecase/__init__.py +3 -0
  118. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/usecase/bootstrap.py +24 -0
  119. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/usecase/export_usage.py +93 -0
  120. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/usecase/list_jobs.py +31 -0
  121. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/usecase/list_sessions.py +34 -0
  122. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/usecase/list_workflows.py +28 -0
  123. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/usecase/new_workflow.py +109 -0
  124. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/usecase/render_statusline.py +117 -0
  125. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/usecase/set_credential.py +27 -0
  126. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/usecase/start_job.py +125 -0
  127. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/wiring/__init__.py +3 -0
  128. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/application/wiring/composition.py +230 -0
  129. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/common/__init__.py +3 -0
  130. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/common/config.py +179 -0
  131. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/common/log.py +83 -0
  132. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/common/paths.py +25 -0
  133. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/common/spec_loader.py +67 -0
  134. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/py.typed +0 -0
  135. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/resources/workflows/_common/base.md +25 -0
  136. generic_ml_wrapper-0.1.0/src/generic_ml_wrapper/resources/workflows/create-workflow/workflow.md +35 -0
  137. generic_ml_wrapper-0.1.0/tests/_conformance.py +301 -0
  138. generic_ml_wrapper-0.1.0/tests/conftest.py +34 -0
  139. generic_ml_wrapper-0.1.0/tests/test_anthropic_sse.py +90 -0
  140. generic_ml_wrapper-0.1.0/tests/test_authoring_separation.py +42 -0
  141. generic_ml_wrapper-0.1.0/tests/test_banner.py +28 -0
  142. generic_ml_wrapper-0.1.0/tests/test_bootstrap.py +51 -0
  143. generic_ml_wrapper-0.1.0/tests/test_caller_loader.py +45 -0
  144. generic_ml_wrapper-0.1.0/tests/test_callers.py +499 -0
  145. generic_ml_wrapper-0.1.0/tests/test_cli.py +505 -0
  146. generic_ml_wrapper-0.1.0/tests/test_compressor.py +88 -0
  147. generic_ml_wrapper-0.1.0/tests/test_config.py +92 -0
  148. generic_ml_wrapper-0.1.0/tests/test_context_file.py +36 -0
  149. generic_ml_wrapper-0.1.0/tests/test_credentials_store.py +80 -0
  150. generic_ml_wrapper-0.1.0/tests/test_cursor_status_parser.py +54 -0
  151. generic_ml_wrapper-0.1.0/tests/test_export_usage_usecase.py +89 -0
  152. generic_ml_wrapper-0.1.0/tests/test_filesystem_transcript_store.py +43 -0
  153. generic_ml_wrapper-0.1.0/tests/test_filesystem_workflow_source.py +135 -0
  154. generic_ml_wrapper-0.1.0/tests/test_identifiers.py +63 -0
  155. generic_ml_wrapper-0.1.0/tests/test_interceptor_chain.py +65 -0
  156. generic_ml_wrapper-0.1.0/tests/test_list_jobs_usecase.py +41 -0
  157. generic_ml_wrapper-0.1.0/tests/test_list_sessions_usecase.py +45 -0
  158. generic_ml_wrapper-0.1.0/tests/test_list_workflows_usecase.py +34 -0
  159. generic_ml_wrapper-0.1.0/tests/test_local_workspace_inspector.py +77 -0
  160. generic_ml_wrapper-0.1.0/tests/test_log.py +37 -0
  161. generic_ml_wrapper-0.1.0/tests/test_model_invariants.py +66 -0
  162. generic_ml_wrapper-0.1.0/tests/test_new_workflow_usecase.py +115 -0
  163. generic_ml_wrapper-0.1.0/tests/test_openai_chat.py +66 -0
  164. generic_ml_wrapper-0.1.0/tests/test_openai_responses.py +36 -0
  165. generic_ml_wrapper-0.1.0/tests/test_relay.py +363 -0
  166. generic_ml_wrapper-0.1.0/tests/test_rule_cleaner.py +36 -0
  167. generic_ml_wrapper-0.1.0/tests/test_session_naming.py +21 -0
  168. generic_ml_wrapper-0.1.0/tests/test_size_logger.py +25 -0
  169. generic_ml_wrapper-0.1.0/tests/test_smoke.py +20 -0
  170. generic_ml_wrapper-0.1.0/tests/test_sqlite_per_turn_store.py +38 -0
  171. generic_ml_wrapper-0.1.0/tests/test_sqlite_session_store.py +65 -0
  172. generic_ml_wrapper-0.1.0/tests/test_sqlite_usage_store.py +43 -0
  173. generic_ml_wrapper-0.1.0/tests/test_start_job_usecase.py +234 -0
  174. generic_ml_wrapper-0.1.0/tests/test_status_line_config.py +68 -0
  175. generic_ml_wrapper-0.1.0/tests/test_statusline.py +211 -0
  176. generic_ml_wrapper-0.1.0/tests/test_store_conformance.py +103 -0
  177. generic_ml_wrapper-0.1.0/tests/test_vibe_config.py +70 -0
  178. generic_ml_wrapper-0.1.0/tools/hooks/check-commit-msg.sh +14 -0
  179. generic_ml_wrapper-0.1.0/tools/hooks/guard-branch.sh +23 -0
  180. generic_ml_wrapper-0.1.0/uv.lock +727 -0
@@ -0,0 +1,4 @@
1
+ # Make coverage record file paths relative to the repo root, so the Cobertura
2
+ # report resolves against sonar.sources (src/) in SonarQube Cloud.
3
+ [run]
4
+ relative_files = true
@@ -0,0 +1 @@
1
+ * text=auto
@@ -0,0 +1,112 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ # Cancel superseded runs on the same ref (e.g. rapid pushes to a PR).
10
+ concurrency:
11
+ group: ci-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ # CI is a thin caller of nox (see noxfile.py), run through uv from the committed
18
+ # uv.lock -- so the check here is byte-for-byte the local gate AND every build-time
19
+ # input is pinned: GitHub Actions by commit SHA, uv by [tool.uv] required-version,
20
+ # and nox by the lock (`uv run --frozen nox`). A dependency -- or a tool that runs
21
+ # the gate -- can only move via a deliberate `uv lock --upgrade` or a SHA bump.
22
+ # lint/typecheck/imports/coverage are version-independent (their own single jobs, no
23
+ # --python filter, which would silently drop a session declaring no interpreter);
24
+ # tests fan out across the interpreter matrix.
25
+ env:
26
+ UV_CACHE_DIR: ${{ github.workspace }}/.uv-cache
27
+
28
+ jobs:
29
+ test:
30
+ name: test (${{ matrix.os }}, py${{ matrix.python-version }})
31
+ runs-on: ${{ matrix.os }}
32
+ strategy:
33
+ fail-fast: false
34
+ matrix:
35
+ os: [ubuntu-latest, macos-latest, windows-latest]
36
+ python-version: ["3.11", "3.12", "3.13", "3.14"]
37
+ steps:
38
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
39
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
40
+ with:
41
+ python-version: ${{ matrix.python-version }}
42
+ - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
43
+ - name: Run tests (nox from the lock)
44
+ run: uv run --frozen nox --python ${{ matrix.python-version }} -s tests
45
+
46
+ lint:
47
+ name: lint (ruff)
48
+ runs-on: ubuntu-latest
49
+ steps:
50
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
51
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
52
+ with:
53
+ python-version: "3.13"
54
+ - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
55
+ - name: Lint + format check (nox from the lock)
56
+ run: uv run --frozen nox -s lint
57
+
58
+ typecheck:
59
+ name: typecheck (pyright)
60
+ runs-on: ubuntu-latest
61
+ steps:
62
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
63
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
64
+ with:
65
+ python-version: "3.13"
66
+ - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
67
+ - name: Type check (nox from the lock)
68
+ run: uv run --frozen nox -s typecheck
69
+
70
+ import-contracts:
71
+ name: architecture contracts (import-linter)
72
+ runs-on: ubuntu-latest
73
+ steps:
74
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
75
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
76
+ with:
77
+ python-version: "3.13"
78
+ - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
79
+ - name: Check architecture contracts (nox from the lock)
80
+ run: uv run --frozen nox -s imports
81
+
82
+ coverage:
83
+ name: coverage (>=80%)
84
+ runs-on: ubuntu-latest
85
+ steps:
86
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
87
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
88
+ with:
89
+ python-version: "3.13"
90
+ - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
91
+ - name: Tests with coverage floor (nox from the lock)
92
+ run: uv run --frozen nox -s coverage
93
+
94
+ # A single, stable check that rolls up every gate job, so branch protection can
95
+ # require "ci" instead of every matrix + gate job name.
96
+ ci:
97
+ name: ci
98
+ runs-on: ubuntu-latest
99
+ needs: [test, lint, typecheck, import-contracts, coverage]
100
+ if: ${{ always() }}
101
+ steps:
102
+ - name: All gate jobs passed
103
+ run: |
104
+ results="${{ needs.test.result }} ${{ needs.lint.result }} ${{ needs.typecheck.result }} ${{ needs.import-contracts.result }} ${{ needs.coverage.result }}"
105
+ echo "gate results: $results"
106
+ for r in $results; do
107
+ if [ "$r" != "success" ]; then
108
+ echo "a gate job did not pass"
109
+ exit 1
110
+ fi
111
+ done
112
+ echo "all gate jobs passed"
@@ -0,0 +1,47 @@
1
+ name: PR hygiene
2
+
3
+ # Server-side enforcement of the "no AI attribution" rule. The commit-msg hook
4
+ # (tools/hooks/check-commit-msg.sh) covers commit messages locally, but hooks are
5
+ # opt-in and `--no-verify`-bypassable, and a PR's title/body live only on GitHub.
6
+ # This required check gives the rule teeth: it scans the PR title, body, AND every
7
+ # commit message in the PR, with the SAME regex the local hook uses.
8
+
9
+ on:
10
+ pull_request:
11
+ types: [opened, edited, reopened, synchronize]
12
+
13
+ permissions:
14
+ contents: read
15
+ pull-requests: read
16
+
17
+ jobs:
18
+ no-ai-attribution:
19
+ name: no AI attribution
20
+ runs-on: ubuntu-latest
21
+ env:
22
+ # Kept byte-for-byte in sync with tools/hooks/check-commit-msg.sh.
23
+ PATTERN: 'co-authored-by:[[:space:]]*.*(claude|copilot|chatgpt|gpt-|anthropic|openai)|generated with[[:space:]]+\[?(claude|copilot|chatgpt)|🤖[[:space:]]*generated'
24
+ steps:
25
+ - name: Check PR title and body
26
+ env:
27
+ TITLE: ${{ github.event.pull_request.title }}
28
+ BODY: ${{ github.event.pull_request.body }}
29
+ run: |
30
+ if printf '%s\n%s' "$TITLE" "$BODY" | grep -qiE "$PATTERN"; then
31
+ echo "::error::PR title or body contains AI attribution -- please remove it."
32
+ exit 1
33
+ fi
34
+ echo "OK: no AI attribution in PR title/body."
35
+
36
+ - name: Check commit messages
37
+ env:
38
+ GH_TOKEN: ${{ github.token }}
39
+ run: |
40
+ msgs=$(gh api --paginate \
41
+ "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/commits" \
42
+ --jq '.[].commit.message')
43
+ if printf '%s' "$msgs" | grep -qiE "$PATTERN"; then
44
+ echo "::error::A commit message contains AI attribution -- please amend it."
45
+ exit 1
46
+ fi
47
+ echo "OK: no AI attribution in commit messages."
@@ -0,0 +1,28 @@
1
+ # Publish to PyPI via Trusted Publishing (OIDC) when a GitHub Release is published.
2
+ # No token or secret: PyPI trusts this exact repo + workflow + environment, and
3
+ # GitHub mints a short-lived OIDC credential for the upload. Every build-time input
4
+ # is pinned (Actions by commit SHA, uv by [tool.uv] required-version).
5
+ name: release
6
+
7
+ on:
8
+ release:
9
+ types: [published]
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ pypi-publish:
16
+ name: Build and publish to PyPI
17
+ runs-on: ubuntu-latest
18
+ environment: pypi
19
+ permissions:
20
+ id-token: write # OIDC token for Trusted Publishing
21
+ contents: read
22
+ steps:
23
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
24
+ - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
25
+ - name: Build sdist + wheel
26
+ run: uv build
27
+ - name: Publish to PyPI (Trusted Publishing)
28
+ run: uv publish --trusted-publishing always
@@ -0,0 +1,34 @@
1
+ name: Sonar
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ sonar:
14
+ name: SonarQube Cloud
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
18
+ with:
19
+ fetch-depth: 0 # full history so Sonar can attribute new code and blame
20
+
21
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
22
+ with:
23
+ python-version: "3.13"
24
+
25
+ - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
26
+
27
+ # Thin caller of nox (see noxfile.py): the `sonar` session writes coverage.xml
28
+ # (path in sonar-project.properties) that the scan below ingests.
29
+ - name: Run tests with coverage (nox from the lock)
30
+ run: uv run --frozen nox -s sonar
31
+
32
+ - uses: SonarSource/sonarqube-scan-action@713881670b6b3676cda39549040e2d88c70d582e # v8
33
+ env:
34
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
@@ -0,0 +1,44 @@
1
+ # Byte-compiled / optimized
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Distribution / packaging
7
+ build/
8
+ dist/
9
+ *.egg-info/
10
+ .eggs/
11
+
12
+ # Virtual environments
13
+ .venv/
14
+ venv/
15
+ env/
16
+
17
+ # Test / coverage / tooling caches
18
+ .pytest_cache/
19
+ .coverage
20
+ .coverage.*
21
+ htmlcov/
22
+ .nox/
23
+ .ruff_cache/
24
+ coverage.xml
25
+
26
+ # Editors / OS / tools
27
+ .idea/
28
+ .vscode/
29
+ .claude/
30
+ .DS_Store
31
+ Thumbs.db
32
+
33
+ # Secrets — nothing secret belongs in this public repo, but guard anyway
34
+ .env
35
+ *.pem
36
+ *.key
37
+ secrets/
38
+ credentials.toml
39
+
40
+ # Local bootstrap/publish tooling — never published.
41
+ # secret-audit.sh contains the literal private terms it scans for, so it must
42
+ # not be committed (it would reintroduce the very leak it checks for).
43
+ /setup-repo.sh
44
+ /secret-audit.sh
@@ -0,0 +1,40 @@
1
+ [importlinter]
2
+ root_package = generic_ml_wrapper
3
+
4
+ # Rule 1 — domain purity. The innermost ring models the world; it must not import
5
+ # use cases, ports, the composition root, or any adapter. (The interceptor_chain ->
6
+ # port.outbound.interceptor edge this catches was the sole violation at review time,
7
+ # fixed by defining the Interceptor abstraction in the domain and having the outbound
8
+ # InterceptorPort extend it.)
9
+ [importlinter:contract:domain-purity]
10
+ name = Domain must not import use cases, ports, wiring, or adapters
11
+ type = forbidden
12
+ source_modules =
13
+ generic_ml_wrapper.application.domain
14
+ forbidden_modules =
15
+ generic_ml_wrapper.application.usecase
16
+ generic_ml_wrapper.application.port
17
+ generic_ml_wrapper.application.wiring
18
+ generic_ml_wrapper.adapter
19
+
20
+ # Rule 2 — the application ring (domain + use cases + ports) is independent of the
21
+ # adapter and composition layers. Dependencies point inward, never outward.
22
+ [importlinter:contract:application-ring]
23
+ name = Application ring must not import adapters or the composition root
24
+ type = forbidden
25
+ source_modules =
26
+ generic_ml_wrapper.application.domain
27
+ generic_ml_wrapper.application.usecase
28
+ generic_ml_wrapper.application.port
29
+ forbidden_modules =
30
+ generic_ml_wrapper.adapter
31
+ generic_ml_wrapper.application.wiring
32
+
33
+ # Rule 3 — ports are abstractions; use cases depend on them, never the reverse.
34
+ [importlinter:contract:ports-no-usecases]
35
+ name = Ports must not import use cases
36
+ type = forbidden
37
+ source_modules =
38
+ generic_ml_wrapper.application.port
39
+ forbidden_modules =
40
+ generic_ml_wrapper.application.usecase
@@ -0,0 +1,57 @@
1
+ # .pre-commit-config.yaml -- the gates that run BEFORE a commit is written.
2
+ #
3
+ # A failing hook rejects the commit, so malformed code can never reach local
4
+ # history (and therefore can never be pushed). The linters are local hooks backed
5
+ # by the project's own .venv, so no extra environment and no network is needed.
6
+ #
7
+ # One-time setup after cloning:
8
+ # nox -s dev
9
+ # .venv/bin/pre-commit install
10
+ # .venv/bin/pre-commit install --hook-type commit-msg
11
+ #
12
+ # Run manually against all files:
13
+ # .venv/bin/pre-commit run --all-files
14
+ #
15
+ repos:
16
+ - repo: local
17
+ hooks:
18
+ - id: ruff-check
19
+ name: ruff (lint)
20
+ entry: .venv/bin/ruff check .
21
+ language: system
22
+ pass_filenames: false
23
+ types: [python]
24
+
25
+ - id: lint-imports
26
+ name: import-linter (architecture contracts)
27
+ entry: .venv/bin/lint-imports
28
+ language: system
29
+ pass_filenames: false
30
+ types: [python]
31
+
32
+ - id: ruff-format
33
+ name: ruff (format check)
34
+ entry: .venv/bin/ruff format --check .
35
+ language: system
36
+ pass_filenames: false
37
+ types: [python]
38
+
39
+ - id: pyright
40
+ name: pyright (strict type checking)
41
+ entry: .venv/bin/pyright --pythonpath .venv/bin/python
42
+ language: system
43
+ pass_filenames: false
44
+ types: [python]
45
+
46
+ - id: guard-branch
47
+ name: branch guard (no commits to main; naming convention)
48
+ entry: tools/hooks/guard-branch.sh
49
+ language: script
50
+ pass_filenames: false
51
+ always_run: true
52
+
53
+ - id: no-ai-attribution
54
+ name: no AI attribution in commit message
55
+ entry: tools/hooks/check-commit-msg.sh
56
+ language: script
57
+ stages: [commit-msg]
@@ -0,0 +1,31 @@
1
+ # AGENTS.md — working rules for this repo
2
+
3
+ Rules an automated or human contributor must follow. Where a rule can be a
4
+ runnable gate it is one (see `.pre-commit-config.yaml` and `noxfile.py`); prose
5
+ here is the intent behind those gates.
6
+
7
+ ## Process
8
+
9
+ - **Never commit directly to `main`.** Work on a branch named
10
+ `feature/… tech/… fix/… release/… docs/… chore/… test/…`. Enforced by
11
+ `tools/hooks/guard-branch.sh`.
12
+ - **`nox -s green` must pass before every commit** — ruff (lint + format), import
13
+ contracts, pyright strict, tests + coverage floor. The gate that runs locally is the
14
+ one CI runs. `main` requires the `ci` and `no AI attribution` checks (branch protection).
15
+ - **Keep changes small and cohesive**, each landing with its tests and leaving the gate
16
+ green. The pipeline is never red.
17
+ - **No AI/assistant attribution** in commit messages or PRs. Enforced locally by
18
+ `tools/hooks/check-commit-msg.sh` and server-side by the `pr-hygiene` workflow.
19
+
20
+ ## Architecture
21
+
22
+ - The design is `docs/DESIGN.md`; do not re-derive it. Follow the hexagon:
23
+ **dependencies point inward** (domain ← usecase ← ports; adapters depend on
24
+ ports, never the reverse).
25
+ - **Public-clean by construction.** No personal data, employer, internal hosts, or
26
+ private protocols in this repo — real content lives only under `~/.gmlw`. Commit with a
27
+ public identity (a GitHub no-reply address), never a private/work one; the gitignored
28
+ `secret-audit.sh` gates every publish for private terms and secrets.
29
+ - **Strict typing.** pyright strict, zero errors, over **both `src/` and `tests/`**,
30
+ pinned to `pyright==1.1.411`. `# type: ignore` only for a provably-safe case that cannot
31
+ be expressed in the type system, with a comment.
@@ -0,0 +1,45 @@
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/), and the project adheres
5
+ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-07-14
10
+
11
+ First public release — a metering wrapper around ML coding CLIs.
12
+
13
+ ### Added
14
+ - **Jobs & sessions.** Enter at a **job** you tag; the wrapper mints a named,
15
+ resumable **session** on the client and persists it. `gmlw start`, `jobs`,
16
+ `sessions`, `export`.
17
+ - **Four clients** driven the same way: **claude**, **cursor**, **codex**, **vibe**.
18
+ Which one is config-driven (`[client]`) or `--client`.
19
+ - **Metering relay.** A local, capability-URL-authenticated relay records **per-turn
20
+ tokens and cost** for the metered clients (claude/codex/vibe) into a SQLite ledger;
21
+ `gmlw export` reports per-turn rows, per-model totals, and per-session cost.
22
+ - **Status line** for the clients that host one (claude, cursor): git · folder ·
23
+ model · context% · a client-specific allowance block, plus a per-session and
24
+ per-job usage footer.
25
+ - **Workflows.** Author a small operating context once (`gmlw workflow new`, a warm
26
+ create-workflow interview) and launch a job with it; context is compiled from a
27
+ shared base + your profile + rules + the workflow steps, through an interceptor
28
+ chain (with opt-in context compression via `generic-ml-cache`).
29
+ - **Durable provenance.** The exact compiled context is written per session
30
+ (`contexts/<job>/<session>.context.md`); an **opt-in transcript** keeps each metered
31
+ call's request/response/usage.
32
+ - **Credentials.** Per-workflow secrets stored `0600` and injected into the client's
33
+ environment at launch (`gmlw creds set`).
34
+ - **Storage.** A single SQLite ledger (`~/.gmlw/ledger.db`, WAL) for jobs, sessions,
35
+ per-turn usage, and session costs.
36
+ - **Safety.** Validated `JobId` + filesystem containment under an owner-only `~/.gmlw`;
37
+ never overwriting an unparseable client-settings or credentials file.
38
+
39
+ ### Engineering
40
+ - Hexagonal (ports & adapters), enforced by `import-linter`; strict `ruff` + `pyright`
41
+ over `src` and `tests`; `nox` gates mirrored by CI across Python 3.11–3.14; a
42
+ server-side no-AI-attribution check and branch protection.
43
+
44
+ [Unreleased]: https://github.com/danielslobozian/generic-ml-wrapper/compare/v0.1.0...HEAD
45
+ [0.1.0]: https://github.com/danielslobozian/generic-ml-wrapper/releases/tag/v0.1.0
@@ -0,0 +1,89 @@
1
+ # Contributor Covenant 3.0 Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We pledge to make our community welcoming, safe, and equitable for all.
6
+
7
+ We are committed to fostering an environment that respects and promotes the dignity, rights, and contributions of all individuals, regardless of characteristics including race, ethnicity, caste, color, age, physical characteristics, neurodiversity, disability, sex or gender, gender identity or expression, sexual orientation, language, philosophy or religion, national or social origin, socio-economic position, level of education, or other status. The same privileges of participation are extended to everyone who participates in good faith and in accordance with this Covenant.
8
+
9
+
10
+ ## Encouraged Behaviors
11
+
12
+ While acknowledging differences in social norms, we all strive to meet our community's expectations for positive behavior. We also understand that our words and actions may be interpreted differently than we intend based on culture, background, or native language.
13
+
14
+ With these considerations in mind, we agree to behave mindfully toward each other and act in ways that center our shared values, including:
15
+
16
+ 1. Respecting the **purpose of our community**, our activities, and our ways of gathering.
17
+ 2. Engaging **kindly and honestly** with others.
18
+ 3. Respecting **different viewpoints** and experiences.
19
+ 4. **Taking responsibility** for our actions and contributions.
20
+ 5. Gracefully giving and accepting **constructive feedback**.
21
+ 6. Committing to **repairing harm** when it occurs.
22
+ 7. Behaving in other ways that promote and sustain the **well-being of our community**.
23
+
24
+
25
+ ## Restricted Behaviors
26
+
27
+ We agree to restrict the following behaviors in our community. Instances, threats, and promotion of these behaviors are violations of this Code of Conduct.
28
+
29
+ 1. **Harassment.** Violating explicitly expressed boundaries or engaging in unnecessary personal attention after any clear request to stop.
30
+ 2. **Character attacks.** Making insulting, demeaning, or pejorative comments directed at a community member or group of people.
31
+ 3. **Stereotyping or discrimination.** Characterizing anyone’s personality or behavior on the basis of immutable identities or traits.
32
+ 4. **Sexualization.** Behaving in a way that would generally be considered inappropriately intimate in the context or purpose of the community.
33
+ 5. **Violating confidentiality**. Sharing or acting on someone's personal or private information without their permission.
34
+ 6. **Endangerment.** Causing, encouraging, or threatening violence or other harm toward any person or group.
35
+ 7. Behaving in other ways that **threaten the well-being** of our community.
36
+
37
+ ### Other Restrictions
38
+
39
+ 1. **Misleading identity.** Impersonating someone else for any reason, or pretending to be someone else to evade enforcement actions.
40
+ 2. **Failing to credit sources.** Not properly crediting the sources of content you contribute.
41
+ 3. **Promotional materials**. Sharing marketing or other commercial content in a way that is outside the norms of the community.
42
+ 4. **Irresponsible communication.** Failing to responsibly present content which includes, links or describes any other restricted behaviors.
43
+
44
+
45
+ ## Reporting an Issue
46
+
47
+ Tensions can occur between community members even when they are trying their best to collaborate. Not every conflict represents a code of conduct violation, and this Code of Conduct reinforces encouraged behaviors and norms that can help avoid conflicts and minimize harm.
48
+
49
+ When an incident does occur, it is important to report it promptly. To report a possible violation, please use the repository's **private reporting** channel: go to the **Security** tab and choose **Report a vulnerability**, which opens a confidential advisory visible only to the maintainers — note clearly at the top that it is a **Code of Conduct report**, not a security issue. This keeps the report private without requiring an email address. If you would rather not use that channel, or if a maintainer is the subject of the report, you may report the account or content directly to GitHub through [GitHub's report abuse flow](https://github.com/contact/report-abuse). Please do **not** open a public issue for a Code of Conduct concern.
50
+
51
+ The maintainers act as this project's Community Moderators. They take reports of violations seriously and will make every effort to respond in a timely manner. They will investigate all reports of code of conduct violations, reviewing messages, logs, and recordings, or interviewing witnesses and other participants. Community Moderators will keep investigation and enforcement actions as transparent as possible while prioritizing safety and confidentiality. In order to honor these values, enforcement actions are carried out in private with the involved parties, but communicating to the whole community may be part of a mutually agreed upon resolution.
52
+
53
+
54
+ ## Addressing and Repairing Harm
55
+
56
+ If an investigation by the Community Moderators finds that this Code of Conduct has been violated, the following enforcement ladder may be used to determine how best to repair harm, based on the incident's impact on the individuals involved and the community as a whole. Depending on the severity of a violation, lower rungs on the ladder may be skipped.
57
+
58
+ 1) Warning
59
+ 1) Event: A violation involving a single incident or series of incidents.
60
+ 2) Consequence: A private, written warning from the Community Moderators.
61
+ 3) Repair: Examples of repair include a private written apology, acknowledgement of responsibility, and seeking clarification on expectations.
62
+ 2) Temporarily Limited Activities
63
+ 1) Event: A repeated incidence of a violation that previously resulted in a warning, or the first incidence of a more serious violation.
64
+ 2) Consequence: A private, written warning with a time-limited cooldown period designed to underscore the seriousness of the situation and give the community members involved time to process the incident. The cooldown period may be limited to particular communication channels or interactions with particular community members.
65
+ 3) Repair: Examples of repair may include making an apology, using the cooldown period to reflect on actions and impact, and being thoughtful about re-entering community spaces after the period is over.
66
+ 3) Temporary Suspension
67
+ 1) Event: A pattern of repeated violation which the Community Moderators have tried to address with warnings, or a single serious violation.
68
+ 2) Consequence: A private written warning with conditions for return from suspension. In general, temporary suspensions give the person being suspended time to reflect upon their behavior and possible corrective actions.
69
+ 3) Repair: Examples of repair include respecting the spirit of the suspension, meeting the specified conditions for return, and being thoughtful about how to reintegrate with the community when the suspension is lifted.
70
+ 4) Permanent Ban
71
+ 1) Event: A pattern of repeated code of conduct violations that other steps on the ladder have failed to resolve, or a violation so serious that the Community Moderators determine there is no way to keep the community safe with this person as a member.
72
+ 2) Consequence: Access to all community spaces, tools, and communication channels is removed. In general, permanent bans should be rarely used, should have strong reasoning behind them, and should only be resorted to if working through other remedies has failed to change the behavior.
73
+ 3) Repair: There is no possible repair in cases of this severity.
74
+
75
+ This enforcement ladder is intended as a guideline. It does not limit the ability of Community Managers to use their discretion and judgment, in keeping with the best interests of our community.
76
+
77
+
78
+ ## Scope
79
+
80
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public or other spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
81
+
82
+
83
+ ## Attribution
84
+
85
+ This Code of Conduct is adapted from the Contributor Covenant, version 3.0, permanently available at [https://www.contributor-covenant.org/version/3/0/](https://www.contributor-covenant.org/version/3/0/).
86
+
87
+ Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under CC BY-SA 4.0. To view a copy of this license, visit [https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/)
88
+
89
+ For answers to common questions about Contributor Covenant, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are provided at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). Additional enforcement and community guideline resources can be found at [https://www.contributor-covenant.org/resources](https://www.contributor-covenant.org/resources). The enforcement ladder was inspired by the work of [Mozilla’s code of conduct team](https://github.com/mozilla/inclusion).
@@ -0,0 +1,94 @@
1
+ # Contributing
2
+
3
+ Thanks for your interest in `generic-ml-wrapper`. This is an open project under the
4
+ Apache-2.0 license and contributions are welcome — bug reports, documentation, tests,
5
+ and code alike.
6
+
7
+ By participating you agree to abide by the [Code of Conduct](CODE_OF_CONDUCT.md).
8
+
9
+ ## Ways to help
10
+
11
+ - **Report a bug.** Open an issue describing what you ran and what happened. A minimal
12
+ repro — the exact `gmlw` command, the client, and a failing test if you can — is gold.
13
+ - **Propose a feature.** Open an issue and check [`docs/DESIGN.md`](docs/DESIGN.md)
14
+ first — the design invariants there are load-bearing, and your idea may already be
15
+ covered or deliberately out of scope.
16
+ - **Improve the docs.** If something was unclear, a doc fix helps the next person.
17
+ - **Send code.** See below.
18
+
19
+ ## Development setup
20
+
21
+ You need Python 3.11–3.13 and [`uv`](https://docs.astral.sh/uv/). The gates run through
22
+ [`nox`](https://nox.thea.codes/) with the `uv` backend, from the committed `uv.lock`.
23
+
24
+ ```bash
25
+ git clone https://github.com/danielslobozian/generic-ml-wrapper.git
26
+ cd generic-ml-wrapper
27
+ uv sync --extra dev # install the project + dev tools into .venv
28
+ # or: nox -s dev # build the IDE .venv the same way
29
+ pre-commit install # wire the local git hooks (optional but recommended)
30
+ ```
31
+
32
+ The only runtime dependencies are the sibling
33
+ [`generic-ml-cache`](https://github.com/danielslobozian/generic-ml-cache) packages
34
+ (`-core`, `-adapters`, `-bootstrap`) — used by the context compressor. Dev tooling
35
+ (`pytest`, `ruff`, `pyright`, `import-linter`, `nox`, `pre-commit`) lives in the `dev`
36
+ extra / dependency group and is pinned by the lock.
37
+
38
+ ## The gate
39
+
40
+ The gates are defined once in [`noxfile.py`](noxfile.py); CI is a thin caller of them,
41
+ so what runs locally is byte-for-byte what runs in CI.
42
+
43
+ ```bash
44
+ nox # the default gate: lint · imports · typecheck · tests (3.11–3.13)
45
+ nox -s green # the whole gate in one env: lint · format · imports · pyright · coverage
46
+ nox -s tests # just the test matrix
47
+ nox -s coverage # tests with the 80% coverage floor
48
+ ```
49
+
50
+ - **`lint`** — `ruff check` + `ruff format --check` (line length 100, strict rule set).
51
+ - **`typecheck`** — `pyright` in **strict** mode over both `src/` **and** `tests/`
52
+ (pinned to `pyright==1.1.411`; the 3.11 floor is checked deliberately).
53
+ - **`imports`** — `lint-imports`: the three hexagon contracts in `.importlinter`.
54
+ - **`coverage`** — the suite with an 80% floor.
55
+
56
+ The suite is **offline and token-free**: no real client is launched, no network call is
57
+ made, and no model is invoked — external boundaries are faked, and `~/.gmlw` is
58
+ redirected to a temp dir for every test. CI runs it on Linux, macOS, and Windows across
59
+ Python 3.11–3.13; make sure it passes locally before opening a PR.
60
+
61
+ ## Coding guidelines
62
+
63
+ - **Dependencies point inward.** `domain ← usecase ← ports`; adapters depend on ports,
64
+ never the reverse. This is enforced by `import-linter`, not just convention — see the
65
+ hexagon in [`docs/DESIGN.md`](docs/DESIGN.md).
66
+ - **The domain is pure.** Entities and domain services do no I/O and import no adapter.
67
+ A new capability the app *needs* from the world is an outbound **port**, implemented by
68
+ an adapter and wired in `application/wiring/composition.py`.
69
+ - **Strict typing.** `pyright` strict, zero errors. A `# type: ignore` is only for a
70
+ provably-safe case that cannot be expressed in the type system, with a comment.
71
+ - **Public-clean by construction.** No personal data, employer, internal hosts, or work
72
+ identities in the repo — real content lives only under `~/.gmlw`. Commit with a public
73
+ identity (a GitHub no-reply address), not a private/work one.
74
+ - **Cross-platform.** Don't assume an OS; the CI matrix (incl. Windows) will tell on you.
75
+ - **Match the existing style.** `ruff` formats and lints; run `nox -s lint` (or
76
+ `ruff check . && ruff format .`).
77
+
78
+ ## Pull request process
79
+
80
+ 1. Branch from `main` — `feature/… tech/… fix/… docs/… chore/… test/…`. Direct pushes to
81
+ `main` are blocked by branch protection.
82
+ 2. Make your change with tests that cover it, keeping `nox -s green` passing.
83
+ 3. Update [`CHANGELOG.md`](CHANGELOG.md) under `[Unreleased]`.
84
+ 4. Open the pull request and describe the *why*, not just the *what*; link any related
85
+ issue. CI must be green and the **no-AI-attribution** check must pass — commit messages
86
+ and PR text must contain no AI/assistant attribution (a hard project rule, enforced
87
+ both by a local hook and server-side).
88
+
89
+ See [`GOVERNANCE.md`](GOVERNANCE.md) for how decisions get made.
90
+
91
+ ## Reporting security issues
92
+
93
+ Please do **not** open a public issue for a vulnerability. Follow
94
+ [`SECURITY.md`](SECURITY.md) instead.