imbue-mngr-pi-coding-usage 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.
- imbue_mngr_pi_coding_usage-0.1.0/.gitignore +364 -0
- imbue_mngr_pi_coding_usage-0.1.0/CHANGELOG.md +13 -0
- imbue_mngr_pi_coding_usage-0.1.0/PKG-INFO +41 -0
- imbue_mngr_pi_coding_usage-0.1.0/README.md +31 -0
- imbue_mngr_pi_coding_usage-0.1.0/UNABRIDGED_CHANGELOG.md +13 -0
- imbue_mngr_pi_coding_usage-0.1.0/changelog/.gitkeep +0 -0
- imbue_mngr_pi_coding_usage-0.1.0/conftest.py +7 -0
- imbue_mngr_pi_coding_usage-0.1.0/imbue/mngr_pi_coding_usage/__init__.py +0 -0
- imbue_mngr_pi_coding_usage-0.1.0/imbue/mngr_pi_coding_usage/aggregate_usage_source_test.py +56 -0
- imbue_mngr_pi_coding_usage-0.1.0/imbue/mngr_pi_coding_usage/conftest.py +3 -0
- imbue_mngr_pi_coding_usage-0.1.0/imbue/mngr_pi_coding_usage/cross_boundary_test.py +23 -0
- imbue_mngr_pi_coding_usage-0.1.0/imbue/mngr_pi_coding_usage/plugin.py +67 -0
- imbue_mngr_pi_coding_usage-0.1.0/imbue/mngr_pi_coding_usage/plugin_test.py +53 -0
- imbue_mngr_pi_coding_usage-0.1.0/imbue/mngr_pi_coding_usage/test_ratchets.py +278 -0
- imbue_mngr_pi_coding_usage-0.1.0/pyproject.toml +68 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
**/__pycache__/
|
|
3
|
+
**/*.py[codz]
|
|
4
|
+
**/*$py.class
|
|
5
|
+
|
|
6
|
+
# macOS Finder metadata
|
|
7
|
+
**/.DS_Store
|
|
8
|
+
|
|
9
|
+
# C extensions
|
|
10
|
+
**/*.so
|
|
11
|
+
|
|
12
|
+
# Distribution / packaging
|
|
13
|
+
**/.Python
|
|
14
|
+
**/build/
|
|
15
|
+
**/develop-eggs/
|
|
16
|
+
**/dist/
|
|
17
|
+
**/downloads/
|
|
18
|
+
**/eggs/
|
|
19
|
+
**/.eggs/
|
|
20
|
+
**/lib/
|
|
21
|
+
**/lib64/
|
|
22
|
+
**/parts/
|
|
23
|
+
**/sdist/
|
|
24
|
+
**/var/
|
|
25
|
+
**/wheels/
|
|
26
|
+
**/share/python-wheels/
|
|
27
|
+
**/*.egg-info/
|
|
28
|
+
**/.installed.cfg
|
|
29
|
+
**/*.egg
|
|
30
|
+
**/MANIFEST
|
|
31
|
+
|
|
32
|
+
# PyInstaller
|
|
33
|
+
# Usually these files are written by a python script from a template
|
|
34
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
35
|
+
**/*.manifest
|
|
36
|
+
**/*.spec
|
|
37
|
+
|
|
38
|
+
# Installer logs
|
|
39
|
+
**/pip-log.txt
|
|
40
|
+
**/pip-delete-this-directory.txt
|
|
41
|
+
|
|
42
|
+
# Unit test / coverage reports
|
|
43
|
+
**/htmlcov/
|
|
44
|
+
**/.tox/
|
|
45
|
+
**/.nox/
|
|
46
|
+
**/.coverage
|
|
47
|
+
**/.coverage.*
|
|
48
|
+
**/.cache
|
|
49
|
+
**/nosetests.xml
|
|
50
|
+
**/coverage.xml
|
|
51
|
+
**/*.cover
|
|
52
|
+
**/*.py.cover
|
|
53
|
+
**/.hypothesis/
|
|
54
|
+
**/.pytest_cache/
|
|
55
|
+
**/cover/
|
|
56
|
+
|
|
57
|
+
# Translations
|
|
58
|
+
**/*.mo
|
|
59
|
+
**/*.pot
|
|
60
|
+
|
|
61
|
+
# Django stuff:
|
|
62
|
+
**/*.log
|
|
63
|
+
**/local_settings.py
|
|
64
|
+
**/db.sqlite3
|
|
65
|
+
**/db.sqlite3-journal
|
|
66
|
+
|
|
67
|
+
# Flask stuff:
|
|
68
|
+
**/instance/
|
|
69
|
+
**/.webassets-cache
|
|
70
|
+
|
|
71
|
+
# Scrapy stuff:
|
|
72
|
+
**/.scrapy
|
|
73
|
+
|
|
74
|
+
# Sphinx documentation
|
|
75
|
+
**/docs/_build/
|
|
76
|
+
|
|
77
|
+
# PyBuilder
|
|
78
|
+
**/.pybuilder/
|
|
79
|
+
**/target/
|
|
80
|
+
|
|
81
|
+
# Jupyter Notebook
|
|
82
|
+
**/.ipynb_checkpoints
|
|
83
|
+
|
|
84
|
+
# IPython
|
|
85
|
+
**/profile_default/
|
|
86
|
+
**/ipython_config.py
|
|
87
|
+
|
|
88
|
+
# pyenv
|
|
89
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
90
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
91
|
+
# .python-version
|
|
92
|
+
|
|
93
|
+
# pipenv
|
|
94
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
95
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
96
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
97
|
+
# install all needed dependencies.
|
|
98
|
+
#Pipfile.lock
|
|
99
|
+
|
|
100
|
+
# UV
|
|
101
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
102
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
103
|
+
# commonly ignored for libraries.
|
|
104
|
+
#uv.lock
|
|
105
|
+
|
|
106
|
+
# poetry
|
|
107
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
108
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
109
|
+
# commonly ignored for libraries.
|
|
110
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
111
|
+
#poetry.lock
|
|
112
|
+
#poetry.toml
|
|
113
|
+
|
|
114
|
+
# pdm
|
|
115
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
116
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
117
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
118
|
+
#pdm.lock
|
|
119
|
+
#pdm.toml
|
|
120
|
+
**/.pdm-python
|
|
121
|
+
**/.pdm-build/
|
|
122
|
+
|
|
123
|
+
# pixi
|
|
124
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
125
|
+
#pixi.lock
|
|
126
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
127
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
128
|
+
**/.pixi
|
|
129
|
+
|
|
130
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
131
|
+
**/__pypackages__/
|
|
132
|
+
|
|
133
|
+
# Celery stuff
|
|
134
|
+
**/celerybeat-schedule
|
|
135
|
+
**/celerybeat.pid
|
|
136
|
+
|
|
137
|
+
# SageMath parsed files
|
|
138
|
+
**/*.sage.py
|
|
139
|
+
|
|
140
|
+
# Environments
|
|
141
|
+
**/.env
|
|
142
|
+
**/.envrc
|
|
143
|
+
**/.venv
|
|
144
|
+
**/.venv-*
|
|
145
|
+
**/env/
|
|
146
|
+
**/venv/
|
|
147
|
+
**/ENV/
|
|
148
|
+
**/env.bak/
|
|
149
|
+
**/venv.bak/
|
|
150
|
+
|
|
151
|
+
# Spyder project settings
|
|
152
|
+
**/.spyderproject
|
|
153
|
+
**/.spyproject
|
|
154
|
+
|
|
155
|
+
# Rope project settings
|
|
156
|
+
**/.ropeproject
|
|
157
|
+
|
|
158
|
+
# mkdocs documentation
|
|
159
|
+
**/site
|
|
160
|
+
|
|
161
|
+
# mypy
|
|
162
|
+
**/.mypy_cache/
|
|
163
|
+
**/.dmypy.json
|
|
164
|
+
**/dmypy.json
|
|
165
|
+
|
|
166
|
+
# Pyre type checker
|
|
167
|
+
**/.pyre/
|
|
168
|
+
|
|
169
|
+
# pytype static type analyzer
|
|
170
|
+
**/.pytype/
|
|
171
|
+
|
|
172
|
+
# Cython debug symbols
|
|
173
|
+
**/cython_debug/
|
|
174
|
+
|
|
175
|
+
# PyCharm
|
|
176
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
177
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
178
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
179
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
180
|
+
**/.idea/
|
|
181
|
+
|
|
182
|
+
# Abstra
|
|
183
|
+
# Abstra is an AI-powered process automation framework.
|
|
184
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
185
|
+
# Learn more at https://abstra.io/docs
|
|
186
|
+
**/.abstra/
|
|
187
|
+
|
|
188
|
+
# Visual Studio Code
|
|
189
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
190
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
191
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
192
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
193
|
+
# .vscode/
|
|
194
|
+
|
|
195
|
+
# Ruff stuff:
|
|
196
|
+
**/.ruff_cache/
|
|
197
|
+
|
|
198
|
+
# PyPI configuration file
|
|
199
|
+
**/.pypirc
|
|
200
|
+
|
|
201
|
+
# Cursor
|
|
202
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
203
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
204
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
205
|
+
**/.cursorignore
|
|
206
|
+
**/.cursorindexingignore
|
|
207
|
+
|
|
208
|
+
# Marimo
|
|
209
|
+
**/marimo/_static/
|
|
210
|
+
**/marimo/_lsp/
|
|
211
|
+
**/__marimo__/
|
|
212
|
+
|
|
213
|
+
# task folders for local custom claude workflow (at any depth, incl. the
|
|
214
|
+
# root-level `dev` project's `dev/_tasks/`, which `*/*/_tasks/` missed)
|
|
215
|
+
**/_tasks/
|
|
216
|
+
**/.sesskey
|
|
217
|
+
|
|
218
|
+
# Node.js
|
|
219
|
+
**/node_modules/
|
|
220
|
+
|
|
221
|
+
# Frontend build artifacts
|
|
222
|
+
**/frontend-dist/
|
|
223
|
+
|
|
224
|
+
# Claude Code local settings (session-specific permissions)
|
|
225
|
+
**/.claude/settings.local.json
|
|
226
|
+
|
|
227
|
+
# Local Claude settings backup files
|
|
228
|
+
**/.claude/*.bak
|
|
229
|
+
|
|
230
|
+
# Claude worktress
|
|
231
|
+
**/.claude/worktrees
|
|
232
|
+
|
|
233
|
+
# Agent definitions deployed by mngr_subagent_proxy at session start
|
|
234
|
+
**/.claude/agents/
|
|
235
|
+
|
|
236
|
+
# Local mngr settings (not committed)
|
|
237
|
+
**/.mngr/settings.local.toml
|
|
238
|
+
|
|
239
|
+
# Local working / scratch markdown (not committed)
|
|
240
|
+
**/*.local.md
|
|
241
|
+
|
|
242
|
+
# Local working / scratch shell scripts (not committed)
|
|
243
|
+
# (covers user notification scripts like scripts/notify_user.local.sh, too)
|
|
244
|
+
**/*.local.sh
|
|
245
|
+
|
|
246
|
+
# Local-only throwaway scripts (e.g. one-off test harnesses).
|
|
247
|
+
**/scripts/*.local.sh
|
|
248
|
+
|
|
249
|
+
# Local working / scratch Python + text (not committed; e.g. one-off validation
|
|
250
|
+
# harnesses or probe scripts). Mirrors the *.local.md / *.local.sh convention.
|
|
251
|
+
**/*.local.py
|
|
252
|
+
**/*.local.txt
|
|
253
|
+
|
|
254
|
+
# Test output files (slow tests, coverage summaries)
|
|
255
|
+
**/.test_output/
|
|
256
|
+
|
|
257
|
+
# TMR output directories (reports + test outputs)
|
|
258
|
+
**/tmr_*/
|
|
259
|
+
**/tmr-report/
|
|
260
|
+
|
|
261
|
+
# Active session marker file (used to detect interrupted sessions)
|
|
262
|
+
**/.claude/active
|
|
263
|
+
|
|
264
|
+
# we stick the image build artifacts here
|
|
265
|
+
**/.mngr/dev/build/
|
|
266
|
+
**/.mngr/dev/secrets/
|
|
267
|
+
|
|
268
|
+
# Offload caches and local files
|
|
269
|
+
**/.offload/**
|
|
270
|
+
**/test-results
|
|
271
|
+
|
|
272
|
+
# Minds deploy-time build artifacts.
|
|
273
|
+
# Ignore contents so we can un-ignore the committed `template/` subtree: git
|
|
274
|
+
# won't descend into a fully-ignored directory, so negations must live below
|
|
275
|
+
# the contents pattern rather than below the directory pattern.
|
|
276
|
+
**/.minds/*
|
|
277
|
+
# The template directory is committed: it defines the expected keys for each
|
|
278
|
+
# <service>.sh file, with empty values. `scripts/push_modal_secrets.py` uses
|
|
279
|
+
# it to check that per-env .sh files declare every template key.
|
|
280
|
+
!/.minds/template/
|
|
281
|
+
!/.minds/template/**
|
|
282
|
+
# Vault ACL policies for the minds tiers. The HCL is non-sensitive and is
|
|
283
|
+
# the source of truth for the policies pushed to Vault via
|
|
284
|
+
# `vault policy write <name> .minds/policies/<name>.hcl`.
|
|
285
|
+
!/.minds/policies/
|
|
286
|
+
!/.minds/policies/**
|
|
287
|
+
|
|
288
|
+
# Autofix session artifacts
|
|
289
|
+
**/.autofix/
|
|
290
|
+
**/.reviewer/logs/
|
|
291
|
+
**/.reviewer/outputs/
|
|
292
|
+
**/.reviewer/settings.local.json
|
|
293
|
+
**/.reviewer/.stop_hook_consecutive_blocks
|
|
294
|
+
**/.reviewer/conversation-issues.jsonl
|
|
295
|
+
**/.reviews/
|
|
296
|
+
|
|
297
|
+
# Stuck agent tracking (stop hook escape valve)
|
|
298
|
+
**/.claude/blocked_stop_commits
|
|
299
|
+
|
|
300
|
+
# Scheduled tasks lock file
|
|
301
|
+
**/.claude/scheduled_tasks.lock
|
|
302
|
+
|
|
303
|
+
# Demo recordings (asciinema .cast, .txt, and scripts)
|
|
304
|
+
**/.demos/
|
|
305
|
+
|
|
306
|
+
# for git worktrees from other repos
|
|
307
|
+
**/.external_worktrees/
|
|
308
|
+
|
|
309
|
+
# ignore coverage files generated within subprojects
|
|
310
|
+
**/cov.xml
|
|
311
|
+
|
|
312
|
+
# ignore demos
|
|
313
|
+
**/recordings
|
|
314
|
+
apps/minds_workspace_server/package-lock.json
|
|
315
|
+
**/*.local.md
|
|
316
|
+
|
|
317
|
+
# Generated by the _generate-dockerignore justfile recipe before each
|
|
318
|
+
# test-offload* run. The recipe strips this self-reference so offload's
|
|
319
|
+
# copy_untracked_files ships the generated file into the build context.
|
|
320
|
+
/.dockerignore
|
|
321
|
+
|
|
322
|
+
# Transient offload image-cache marker written by test-offload runs; not
|
|
323
|
+
# checked in (unlike the committed .offload-*-cache-key files which pin CI
|
|
324
|
+
# image versions).
|
|
325
|
+
/.offload-image-cache
|
|
326
|
+
|
|
327
|
+
# Generated by _generate-release-dockerfile from Dockerfile + .release.extras.
|
|
328
|
+
/libs/mngr/imbue/mngr/resources/Dockerfile.release
|
|
329
|
+
**/.reviewer/autofix/issues/
|
|
330
|
+
.reviewer/logs/
|
|
331
|
+
|
|
332
|
+
# Tailwind Play CDN JS for the minds desktop client. Fetched once via
|
|
333
|
+
# `just minds-tailwind` (plain curl, no build) and shipped with the
|
|
334
|
+
# distributable. Not checked in because it's a ~300KB blob we can always
|
|
335
|
+
# re-fetch.
|
|
336
|
+
/apps/minds/imbue/minds/desktop_client/static/tailwind.js
|
|
337
|
+
|
|
338
|
+
# macOS Finder metadata
|
|
339
|
+
**/.DS_Store
|
|
340
|
+
**/.env.swp
|
|
341
|
+
# Vim swap files for any file (`.foo.swp`, `.foo.swo`, etc.) -- generated
|
|
342
|
+
# while a file is open in vim. Never checked in.
|
|
343
|
+
**/.*.sw[a-p]
|
|
344
|
+
|
|
345
|
+
# `minds env deploy` writes a recover-target file at the monorepo root
|
|
346
|
+
# on failed deploys; `minds env recover` deletes it after rolling back.
|
|
347
|
+
# Never checked in. Per-env naming
|
|
348
|
+
# (``.minds-deploy-recover-target-<env-name>.json``) means each in-flight
|
|
349
|
+
# deploy has its own file, so concurrent deploys against different envs
|
|
350
|
+
# don't trip over each other.
|
|
351
|
+
**/.minds-deploy-*.json
|
|
352
|
+
# Per-env deploy lock files (kernel-level ``flock`` targets). Created
|
|
353
|
+
# by ``hold_deploy_lock`` in ``apps/minds/imbue/minds/envs/recover.py``
|
|
354
|
+
# and held for the entire duration of ``minds env deploy`` /
|
|
355
|
+
# ``minds env recover`` so two concurrent invocations against the same
|
|
356
|
+
# env serialize. The file itself is empty and cheap to leave around.
|
|
357
|
+
**/.minds-deploy-lock-*.lock
|
|
358
|
+
**/.antigravitycli/
|
|
359
|
+
|
|
360
|
+
# Playwright MCP scratch artifacts (mock screenshots, page snapshots)
|
|
361
|
+
**/.playwright-mcp/
|
|
362
|
+
|
|
363
|
+
# tmr (task-runner) orchestration scratch report
|
|
364
|
+
**/tmr-report/
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog - mngr_pi_coding_usage
|
|
2
|
+
|
|
3
|
+
A concise, human-friendly summary of changes for the `mngr_pi_coding_usage` library. Entries are categorized using the [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) categories: Added, Changed, Deprecated, Removed, Fixed, Security.
|
|
4
|
+
|
|
5
|
+
For the full, unedited changelog entries, see [UNABRIDGED_CHANGELOG.md](UNABRIDGED_CHANGELOG.md).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [v0.1.0] - 2026-06-16
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added: New package `imbue-mngr-pi-coding-usage` providing cost/usage tracking for pi agents in `mngr usage`. pi reports per-message cost client-side, so it's REPORTED (not estimated) and aggregated session-incrementally. The per-message writer lives in `mngr_pi_coding`'s lifecycle extension (pi loads a single explicit extension); this package owns the reader (an `aggregate_usage_source` hookimpl claiming the `pi-coding` source) and provisions a `pi_emit_usage` gate marker so the extension only emits usage events when this package is installed.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: imbue-mngr-pi-coding-usage
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: pi usage data provider for `mngr usage` (reader + writer gate; the per-message writer lives in mngr_pi_coding's lifecycle extension)
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Requires-Dist: imbue-mngr-pi-coding==0.1.13
|
|
7
|
+
Requires-Dist: imbue-mngr-usage==0.1.5
|
|
8
|
+
Requires-Dist: imbue-mngr==0.2.16
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# imbue-mngr-pi-coding-usage
|
|
12
|
+
|
|
13
|
+
pi data provider for `mngr usage`. pi loads a single explicit extension
|
|
14
|
+
(mngr_pi_coding's lifecycle extension, via `pi -e`), so the per-message usage
|
|
15
|
+
**writer** lives there -- it already holds each assistant message's cost and
|
|
16
|
+
tokens. This package owns the two usage-specific pieces:
|
|
17
|
+
|
|
18
|
+
- **The reader**: an `aggregate_usage_source` hookimpl claiming the `pi-coding`
|
|
19
|
+
source, aggregated session-incrementally (pi reports cost per message, summed
|
|
20
|
+
per session).
|
|
21
|
+
- **The writer gate**: `on_after_provisioning` drops a `pi_emit_usage` marker in
|
|
22
|
+
each pi agent's state dir. The lifecycle extension only emits usage events when
|
|
23
|
+
that marker is present -- so the events are written exactly when their reader
|
|
24
|
+
(this package) is installed.
|
|
25
|
+
|
|
26
|
+
## What gets captured
|
|
27
|
+
|
|
28
|
+
pi computes per-message cost client-side (`usage.cost.total`), so cost is
|
|
29
|
+
**REPORTED** (no estimation). Each assistant `message_end` appends one
|
|
30
|
+
`cost_snapshot` to `events/pi-coding/usage/events.jsonl` with the reported cost,
|
|
31
|
+
tokens (`input`/`output`/`cacheRead`/`cacheWrite`), the provider-qualified model
|
|
32
|
+
(`<provider>/<model>`), and the session id (the pi session file's basename).
|
|
33
|
+
`cost_mode` is `API_KEY` (pi bills a real provider key).
|
|
34
|
+
|
|
35
|
+
## Why the writer is in the harness extension
|
|
36
|
+
|
|
37
|
+
Unlike Claude (statusline shim) or OpenCode (auto-loaded `plugin/*.ts`), pi loads
|
|
38
|
+
exactly one explicit extension owned by mngr_pi_coding. A standalone writer would
|
|
39
|
+
require a harness change either way, so the writer is folded into the lifecycle
|
|
40
|
+
extension (which already extracts the message usage), and this package stays the
|
|
41
|
+
reader + gate.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# imbue-mngr-pi-coding-usage
|
|
2
|
+
|
|
3
|
+
pi data provider for `mngr usage`. pi loads a single explicit extension
|
|
4
|
+
(mngr_pi_coding's lifecycle extension, via `pi -e`), so the per-message usage
|
|
5
|
+
**writer** lives there -- it already holds each assistant message's cost and
|
|
6
|
+
tokens. This package owns the two usage-specific pieces:
|
|
7
|
+
|
|
8
|
+
- **The reader**: an `aggregate_usage_source` hookimpl claiming the `pi-coding`
|
|
9
|
+
source, aggregated session-incrementally (pi reports cost per message, summed
|
|
10
|
+
per session).
|
|
11
|
+
- **The writer gate**: `on_after_provisioning` drops a `pi_emit_usage` marker in
|
|
12
|
+
each pi agent's state dir. The lifecycle extension only emits usage events when
|
|
13
|
+
that marker is present -- so the events are written exactly when their reader
|
|
14
|
+
(this package) is installed.
|
|
15
|
+
|
|
16
|
+
## What gets captured
|
|
17
|
+
|
|
18
|
+
pi computes per-message cost client-side (`usage.cost.total`), so cost is
|
|
19
|
+
**REPORTED** (no estimation). Each assistant `message_end` appends one
|
|
20
|
+
`cost_snapshot` to `events/pi-coding/usage/events.jsonl` with the reported cost,
|
|
21
|
+
tokens (`input`/`output`/`cacheRead`/`cacheWrite`), the provider-qualified model
|
|
22
|
+
(`<provider>/<model>`), and the session id (the pi session file's basename).
|
|
23
|
+
`cost_mode` is `API_KEY` (pi bills a real provider key).
|
|
24
|
+
|
|
25
|
+
## Why the writer is in the harness extension
|
|
26
|
+
|
|
27
|
+
Unlike Claude (statusline shim) or OpenCode (auto-loaded `plugin/*.ts`), pi loads
|
|
28
|
+
exactly one explicit extension owned by mngr_pi_coding. A standalone writer would
|
|
29
|
+
require a harness change either way, so the writer is folded into the lifecycle
|
|
30
|
+
extension (which already extracts the message usage), and this package stays the
|
|
31
|
+
reader + gate.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Unabridged Changelog - mngr_pi_coding_usage
|
|
2
|
+
|
|
3
|
+
Full, unedited changelog entries consolidated nightly from individual files in `libs/mngr_pi_coding_usage/changelog/`.
|
|
4
|
+
|
|
5
|
+
For a concise summary, see [CHANGELOG.md](CHANGELOG.md).
|
|
6
|
+
|
|
7
|
+
## 2026-06-16
|
|
8
|
+
|
|
9
|
+
Corrected the package version back to 0.1.0 (it had been bumped to 0.1.1, but that version was never actually released).
|
|
10
|
+
|
|
11
|
+
## 2026-06-16
|
|
12
|
+
|
|
13
|
+
New package `imbue-mngr-pi-coding-usage`: cost/usage tracking for pi agents in `mngr usage`. pi reports per-message cost client-side, so it's REPORTED (not estimated) and aggregated session-incrementally. Because pi loads a single explicit extension, the per-message writer lives in mngr_pi_coding's lifecycle extension; this package owns the reader (an `aggregate_usage_source` hookimpl claiming the `pi-coding` source) and provisions a `pi_emit_usage` gate marker so the extension only emits usage events when this package (their reader) is installed.
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Unit tests for the pi ``aggregate_usage_source`` reader hookimpl."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from imbue.mngr_pi_coding_usage.plugin import aggregate_usage_source
|
|
8
|
+
from imbue.mngr_usage.api import parse_usage_events
|
|
9
|
+
from imbue.mngr_usage.data_types import CostMode
|
|
10
|
+
from imbue.mngr_usage.data_types import CostProvenance
|
|
11
|
+
|
|
12
|
+
_NOW = 2_800_000_000
|
|
13
|
+
_SINCE = 10**12
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _message_event(session_id: str, event_id: str, second: int, total_cost_usd: float) -> dict[str, Any]:
|
|
17
|
+
return {
|
|
18
|
+
"timestamp": f"2056-01-01T00:00:{second:02d}.000000000Z",
|
|
19
|
+
"session_id": session_id,
|
|
20
|
+
"event_id": event_id,
|
|
21
|
+
"cost": {"total_cost_usd": total_cost_usd},
|
|
22
|
+
"model": "anthropic/claude-opus-4-8",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_pi_hookimpl_sums_per_message_as_reported() -> None:
|
|
27
|
+
snapshot = aggregate_usage_source(
|
|
28
|
+
source_name="pi-coding",
|
|
29
|
+
agents_events={
|
|
30
|
+
"agent-1": parse_usage_events(
|
|
31
|
+
[
|
|
32
|
+
_message_event("s1", "evt-pi-usage-0", 1, 0.01),
|
|
33
|
+
_message_event("s1", "evt-pi-usage-1", 2, 0.02),
|
|
34
|
+
],
|
|
35
|
+
"pi-coding",
|
|
36
|
+
)
|
|
37
|
+
},
|
|
38
|
+
since_seconds=_SINCE,
|
|
39
|
+
now=_NOW,
|
|
40
|
+
)
|
|
41
|
+
assert snapshot is not None
|
|
42
|
+
assert snapshot.source_name == "pi-coding"
|
|
43
|
+
record = snapshot.sessions[0]
|
|
44
|
+
assert record.cost.total_cost_usd == 0.03
|
|
45
|
+
assert record.cost_provenance == CostProvenance.REPORTED
|
|
46
|
+
assert record.cost_mode == CostMode.API_KEY
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_pi_hookimpl_declines_non_pi_sources() -> None:
|
|
50
|
+
snapshot = aggregate_usage_source(
|
|
51
|
+
source_name="opencode",
|
|
52
|
+
agents_events={"agent-1": parse_usage_events([_message_event("s1", "evt-pi-usage-0", 1, 0.01)], "opencode")},
|
|
53
|
+
since_seconds=_SINCE,
|
|
54
|
+
now=_NOW,
|
|
55
|
+
)
|
|
56
|
+
assert snapshot is None
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Guard the cross-package writer <-> reader seam.
|
|
2
|
+
|
|
3
|
+
pi's usage *writer* lives in mngr_pi_coding's lifecycle extension (pi loads one
|
|
4
|
+
explicit extension, owned by the harness), while the reader + gate live here.
|
|
5
|
+
The two packages agree only by hand-synced string literals across both a
|
|
6
|
+
language *and* a package boundary -- the gate filename and the source name. A
|
|
7
|
+
rename on either side silently disables usage tracking, so pin the invariant.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from imbue.mngr_pi_coding.plugin import _LIFECYCLE_EXTENSION_NAME
|
|
13
|
+
from imbue.mngr_pi_coding.plugin import _load_resource
|
|
14
|
+
from imbue.mngr_pi_coding_usage.plugin import _PI_USAGE_SOURCE_NAME
|
|
15
|
+
from imbue.mngr_pi_coding_usage.plugin import _USAGE_GATE_FILENAME
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_lifecycle_extension_uses_the_gate_and_source_this_package_owns() -> None:
|
|
19
|
+
lifecycle_ts = _load_resource(_LIFECYCLE_EXTENSION_NAME)
|
|
20
|
+
# The extension only emits usage when it sees this package's gate marker...
|
|
21
|
+
assert f'"{_USAGE_GATE_FILENAME}"' in lifecycle_ts
|
|
22
|
+
# ...and emits under the source the reader here claims (`<name>/usage`).
|
|
23
|
+
assert f'"{_PI_USAGE_SOURCE_NAME}/usage"' in lifecycle_ts
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""pi usage data provider for `mngr usage` (reader + writer gate).
|
|
2
|
+
|
|
3
|
+
pi loads exactly one explicit extension -- mngr_pi_coding's lifecycle extension,
|
|
4
|
+
launched with ``pi -e`` -- so the usage *writer* lives there (it already holds
|
|
5
|
+
each assistant message's cost + tokens). This package owns the two pieces that
|
|
6
|
+
are genuinely usage-specific:
|
|
7
|
+
|
|
8
|
+
1. A gate marker, provisioned per pi agent, that tells the lifecycle extension to
|
|
9
|
+
emit usage events. Without it the writer stays inert -- so usage events are
|
|
10
|
+
only written when *this* package (which ships their reader) is installed.
|
|
11
|
+
Emitting them unread would let ``mngr usage`` fall back to the wrong
|
|
12
|
+
(process-cumulative) strategy and undercount pi's per-message events.
|
|
13
|
+
2. The reader: an ``aggregate_usage_source`` hookimpl claiming the ``pi-coding``
|
|
14
|
+
source, aggregated session-incrementally (pi reports cost per message).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from imbue.mngr import hookimpl
|
|
20
|
+
from imbue.mngr.config.data_types import MngrContext
|
|
21
|
+
from imbue.mngr.hosts.host import get_agent_state_dir_path
|
|
22
|
+
from imbue.mngr.interfaces.agent import AgentInterface
|
|
23
|
+
from imbue.mngr.interfaces.host import OnlineHostInterface
|
|
24
|
+
from imbue.mngr_pi_coding.plugin import PiCodingAgent
|
|
25
|
+
from imbue.mngr_usage.api import aggregate_session_incremental
|
|
26
|
+
from imbue.mngr_usage.data_types import UsageEvent
|
|
27
|
+
from imbue.mngr_usage.data_types import UsageSnapshot
|
|
28
|
+
|
|
29
|
+
# Gate marker the lifecycle extension checks (existsSync) to decide whether to
|
|
30
|
+
# emit usage events. Kept in sync with mngr_pi_lifecycle.ts (literal "pi_emit_usage").
|
|
31
|
+
_USAGE_GATE_FILENAME = "pi_emit_usage"
|
|
32
|
+
|
|
33
|
+
# Source the pi writer emits under ($STATE_DIR/events/pi-coding/usage/...); the
|
|
34
|
+
# reader strips "/usage". A fixed harness id (not the agent subtype), so usage
|
|
35
|
+
# from any pi subtype lumps under one source. Kept in sync with mngr_pi_lifecycle.ts.
|
|
36
|
+
_PI_USAGE_SOURCE_NAME = "pi-coding"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@hookimpl
|
|
40
|
+
def on_after_provisioning(agent: AgentInterface, host: OnlineHostInterface, mngr_ctx: MngrContext) -> None:
|
|
41
|
+
"""Install the usage gate marker for pi agents so the lifecycle extension emits usage events.
|
|
42
|
+
|
|
43
|
+
Skips non-pi agents. Written to the agent state dir, which the extension
|
|
44
|
+
reads (``$MNGR_AGENT_STATE_DIR/pi_emit_usage``) once at startup.
|
|
45
|
+
"""
|
|
46
|
+
if not isinstance(agent, PiCodingAgent):
|
|
47
|
+
return
|
|
48
|
+
state_dir = get_agent_state_dir_path(host.host_dir, agent.id)
|
|
49
|
+
host.write_file(state_dir / _USAGE_GATE_FILENAME, b"1")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@hookimpl
|
|
53
|
+
def aggregate_usage_source(
|
|
54
|
+
source_name: str,
|
|
55
|
+
agents_events: dict[str, list[UsageEvent]],
|
|
56
|
+
since_seconds: int,
|
|
57
|
+
now: int,
|
|
58
|
+
) -> UsageSnapshot | None:
|
|
59
|
+
"""Aggregate the pi usage source; decline every other source.
|
|
60
|
+
|
|
61
|
+
pi reports each assistant message's own cost/tokens (not a cumulative total),
|
|
62
|
+
so this is the session-incremental strategy. Returning None for non-pi sources
|
|
63
|
+
lets the firstresult hook fall through.
|
|
64
|
+
"""
|
|
65
|
+
if source_name != _PI_USAGE_SOURCE_NAME:
|
|
66
|
+
return None
|
|
67
|
+
return aggregate_session_incremental(source_name, agents_events, since_seconds=since_seconds, now=now)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Unit tests for mngr_pi_coding_usage.plugin (gate provisioning + hookimpl filter)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from imbue.mngr.agents.base_agent import BaseAgent
|
|
8
|
+
from imbue.mngr.cli.testing import create_test_agent
|
|
9
|
+
from imbue.mngr.config.data_types import MngrContext
|
|
10
|
+
from imbue.mngr.hosts.host import get_agent_state_dir_path
|
|
11
|
+
from imbue.mngr.primitives import AgentId
|
|
12
|
+
from imbue.mngr.providers.local.instance import LocalProviderInstance
|
|
13
|
+
from imbue.mngr_pi_coding.plugin import PiCodingAgent
|
|
14
|
+
from imbue.mngr_pi_coding.plugin import PiCodingAgentConfig
|
|
15
|
+
from imbue.mngr_pi_coding_usage.plugin import _USAGE_GATE_FILENAME
|
|
16
|
+
from imbue.mngr_pi_coding_usage.plugin import on_after_provisioning
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _gate_path(host_dir: Path, agent_id: AgentId) -> Path:
|
|
20
|
+
return get_agent_state_dir_path(host_dir, agent_id) / _USAGE_GATE_FILENAME
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_provisions_usage_gate_for_pi_agent(
|
|
24
|
+
local_provider: LocalProviderInstance, temp_mngr_ctx: MngrContext, tmp_path: Path
|
|
25
|
+
) -> None:
|
|
26
|
+
work_dir = tmp_path / "work"
|
|
27
|
+
work_dir.mkdir()
|
|
28
|
+
agent = create_test_agent(
|
|
29
|
+
local_provider,
|
|
30
|
+
work_dir,
|
|
31
|
+
agent_config=PiCodingAgentConfig(),
|
|
32
|
+
agent_type=None,
|
|
33
|
+
extra_data=None,
|
|
34
|
+
agent_class=PiCodingAgent,
|
|
35
|
+
)
|
|
36
|
+
on_after_provisioning(agent=agent, host=agent.host, mngr_ctx=temp_mngr_ctx)
|
|
37
|
+
gate = _gate_path(agent.host.host_dir, agent.id)
|
|
38
|
+
assert gate.read_text() == "1"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_skips_non_pi_agent(local_provider: LocalProviderInstance, temp_mngr_ctx: MngrContext, tmp_path: Path) -> None:
|
|
42
|
+
work_dir = tmp_path / "work"
|
|
43
|
+
work_dir.mkdir()
|
|
44
|
+
agent = create_test_agent(
|
|
45
|
+
local_provider,
|
|
46
|
+
work_dir,
|
|
47
|
+
agent_config=None,
|
|
48
|
+
agent_type=None,
|
|
49
|
+
extra_data=None,
|
|
50
|
+
agent_class=BaseAgent,
|
|
51
|
+
)
|
|
52
|
+
on_after_provisioning(agent=agent, host=agent.host, mngr_ctx=temp_mngr_ctx)
|
|
53
|
+
assert not _gate_path(agent.host.host_dir, agent.id).exists()
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from inline_snapshot import snapshot
|
|
5
|
+
|
|
6
|
+
from imbue.imbue_common.ratchet_testing import standard_ratchet_checks as rc
|
|
7
|
+
|
|
8
|
+
_DIR = Path(__file__).parent.parent.parent
|
|
9
|
+
|
|
10
|
+
pytestmark = pytest.mark.xdist_group(name="ratchets")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# --- Code safety ---
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_prevent_todos() -> None:
|
|
17
|
+
rc.check_todos(_DIR, snapshot(0))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_prevent_exec() -> None:
|
|
21
|
+
rc.check_exec(_DIR, snapshot(0))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_prevent_eval() -> None:
|
|
25
|
+
rc.check_eval(_DIR, snapshot(0))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_prevent_while_true() -> None:
|
|
29
|
+
rc.check_while_true(_DIR, snapshot(0))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_prevent_time_sleep() -> None:
|
|
33
|
+
rc.check_time_sleep(_DIR, snapshot(0))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_prevent_global_keyword() -> None:
|
|
37
|
+
rc.check_global_keyword(_DIR, snapshot(0))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_prevent_bare_print() -> None:
|
|
41
|
+
rc.check_bare_print(_DIR, snapshot(0))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# --- Exception handling ---
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_prevent_bare_except() -> None:
|
|
48
|
+
rc.check_bare_except(_DIR, snapshot(0))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_prevent_broad_exception_catch() -> None:
|
|
52
|
+
rc.check_broad_exception_catch(_DIR, snapshot(0))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_prevent_base_exception_catch() -> None:
|
|
56
|
+
rc.check_base_exception_catch(_DIR, snapshot(0))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_prevent_builtin_exception_raises() -> None:
|
|
60
|
+
rc.check_builtin_exception_raises(_DIR, snapshot(0))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_prevent_silent_decode_error_catches() -> None:
|
|
64
|
+
rc.check_silent_decode_error_catches(_DIR, snapshot(0))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# --- Import style ---
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_prevent_inline_imports() -> None:
|
|
71
|
+
rc.check_inline_imports(_DIR, snapshot(0))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_prevent_relative_imports() -> None:
|
|
75
|
+
rc.check_relative_imports(_DIR, snapshot(0))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_prevent_import_datetime() -> None:
|
|
79
|
+
rc.check_import_datetime(_DIR, snapshot(0))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_prevent_importlib_import_module() -> None:
|
|
83
|
+
rc.check_importlib_import_module(_DIR, snapshot(0))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_prevent_getattr() -> None:
|
|
87
|
+
rc.check_getattr(_DIR, snapshot(0))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_prevent_setattr() -> None:
|
|
91
|
+
rc.check_setattr(_DIR, snapshot(0))
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# --- Banned libraries and patterns ---
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_prevent_asyncio_import() -> None:
|
|
98
|
+
rc.check_asyncio_import(_DIR, snapshot(0))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_prevent_pandas_import() -> None:
|
|
102
|
+
rc.check_pandas_import(_DIR, snapshot(0))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_prevent_dataclasses_import() -> None:
|
|
106
|
+
rc.check_dataclasses_import(_DIR, snapshot(0))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_prevent_namedtuple() -> None:
|
|
110
|
+
rc.check_namedtuple(_DIR, snapshot(0))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_prevent_yaml_usage() -> None:
|
|
114
|
+
rc.check_yaml_usage(_DIR, snapshot(0))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_prevent_functools_partial() -> None:
|
|
118
|
+
rc.check_functools_partial(_DIR, snapshot(0))
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_prevent_exit_stack() -> None:
|
|
122
|
+
rc.check_exit_stack(_DIR, snapshot(0))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# --- Hardcoded paths ---
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_prevent_hardcoded_claude_dir() -> None:
|
|
129
|
+
rc.check_hardcoded_claude_dir(_DIR, snapshot(0))
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_prevent_hardcoded_guarded_binary() -> None:
|
|
133
|
+
rc.check_hardcoded_guarded_binary(_DIR, snapshot(0))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# --- Naming conventions ---
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_prevent_num_prefix() -> None:
|
|
140
|
+
rc.check_num_prefix(_DIR, snapshot(0))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# --- Documentation ---
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def test_prevent_trailing_comments() -> None:
|
|
147
|
+
rc.check_trailing_comments(_DIR, snapshot(0))
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_prevent_init_docstrings() -> None:
|
|
151
|
+
rc.check_init_docstrings(_DIR, snapshot(0))
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@pytest.mark.timeout(10)
|
|
155
|
+
def test_prevent_args_in_docstrings() -> None:
|
|
156
|
+
rc.check_args_in_docstrings(_DIR, snapshot(0))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@pytest.mark.timeout(10)
|
|
160
|
+
def test_prevent_returns_in_docstrings() -> None:
|
|
161
|
+
rc.check_returns_in_docstrings(_DIR, snapshot(0))
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# --- Type safety ---
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_prevent_literal_with_multiple_options() -> None:
|
|
168
|
+
rc.check_literal_with_multiple_options(_DIR, snapshot(0))
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_prevent_bare_generic_types() -> None:
|
|
172
|
+
rc.check_bare_generic_types(_DIR, snapshot(0))
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_prevent_typing_builtin_imports() -> None:
|
|
176
|
+
rc.check_typing_builtin_imports(_DIR, snapshot(0))
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def test_prevent_short_uuid_ids() -> None:
|
|
180
|
+
rc.check_short_uuid_ids(_DIR, snapshot(0))
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# --- Pydantic / models ---
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_prevent_model_copy() -> None:
|
|
187
|
+
rc.check_model_copy(_DIR, snapshot(0))
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# --- Logging ---
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_prevent_fstring_logging() -> None:
|
|
194
|
+
rc.check_fstring_logging(_DIR, snapshot(0))
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def test_prevent_click_echo() -> None:
|
|
198
|
+
rc.check_click_echo(_DIR, snapshot(0))
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def test_prevent_logger_exception() -> None:
|
|
202
|
+
rc.check_logger_exception(_DIR, snapshot(0))
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# --- Testing conventions ---
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_prevent_unittest_mock_imports() -> None:
|
|
209
|
+
rc.check_unittest_mock_imports(_DIR, snapshot(0))
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def test_prevent_monkeypatch_setattr() -> None:
|
|
213
|
+
rc.check_monkeypatch_setattr(_DIR, snapshot(0))
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def test_prevent_test_container_classes() -> None:
|
|
217
|
+
rc.check_test_container_classes(_DIR, snapshot(0))
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def test_prevent_pytest_mark_integration() -> None:
|
|
221
|
+
rc.check_pytest_mark_integration(_DIR, snapshot(0))
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
# --- Process management ---
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def test_prevent_os_fork() -> None:
|
|
228
|
+
rc.check_os_fork(_DIR, snapshot(0))
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def test_prevent_bare_urwid_tty_signal_keys() -> None:
|
|
232
|
+
rc.check_bare_urwid_tty_signal_keys(_DIR, snapshot(0))
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def test_prevent_direct_subprocess() -> None:
|
|
236
|
+
rc.check_direct_subprocess(_DIR, snapshot(0))
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def test_prevent_bare_tmux_targets() -> None:
|
|
240
|
+
rc.check_bare_tmux_targets(_DIR, snapshot(0))
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# --- AST-based ratchets ---
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def test_prevent_if_elif_without_else() -> None:
|
|
247
|
+
rc.check_if_elif_without_else(_DIR, snapshot(0))
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def test_prevent_inline_functions() -> None:
|
|
251
|
+
rc.check_inline_functions(_DIR, snapshot(0))
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def test_prevent_underscore_imports() -> None:
|
|
255
|
+
rc.check_underscore_imports(_DIR, snapshot(0))
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def test_prevent_init_methods_in_non_exception_classes() -> None:
|
|
259
|
+
rc.check_init_methods_in_non_exception_classes(_DIR, snapshot(0))
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def test_prevent_cast_usage() -> None:
|
|
263
|
+
rc.check_cast_usage(_DIR, snapshot(0))
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def test_prevent_assert_isinstance() -> None:
|
|
267
|
+
rc.check_assert_isinstance(_DIR, snapshot(0))
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def test_prevent_per_file_host_upload() -> None:
|
|
271
|
+
rc.check_per_file_host_upload(_DIR, snapshot(0))
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
# --- Project-level checks ---
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def test_prevent_code_in_init_files() -> None:
|
|
278
|
+
rc.check_code_in_init_files(_DIR, snapshot(0))
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "imbue-mngr-pi-coding-usage"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "pi usage data provider for `mngr usage` (reader + writer gate; the per-message writer lives in mngr_pi_coding's lifecycle extension)"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"imbue-mngr==0.2.16",
|
|
9
|
+
"imbue-mngr-pi-coding==0.1.13",
|
|
10
|
+
"imbue-mngr-usage==0.1.5",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[project.entry-points.mngr]
|
|
14
|
+
pi_coding_usage = "imbue.mngr_pi_coding_usage.plugin"
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["hatchling"]
|
|
18
|
+
build-backend = "hatchling.build"
|
|
19
|
+
|
|
20
|
+
[tool.hatch.build.targets.wheel]
|
|
21
|
+
packages = ["imbue"]
|
|
22
|
+
exclude = ["*_test.py", "test_*.py", "**/conftest.py", "**/testing.py"]
|
|
23
|
+
|
|
24
|
+
[tool.uv.sources]
|
|
25
|
+
imbue-mngr = { workspace = true }
|
|
26
|
+
imbue-mngr-pi-coding = { workspace = true }
|
|
27
|
+
imbue-mngr-usage = { workspace = true }
|
|
28
|
+
|
|
29
|
+
[tool.pytest.ini_options]
|
|
30
|
+
timeout_func_only = true
|
|
31
|
+
junit_family = "xunit1"
|
|
32
|
+
testpaths = ["."]
|
|
33
|
+
addopts = [
|
|
34
|
+
"--cov=imbue.mngr_pi_coding_usage",
|
|
35
|
+
"--cov-report=term-missing",
|
|
36
|
+
"--cov-report=xml",
|
|
37
|
+
"--durations=20",
|
|
38
|
+
"--timeout=10",
|
|
39
|
+
"--timeout-method=signal",
|
|
40
|
+
"--slow-tests-to-file",
|
|
41
|
+
"--coverage-to-file",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[tool.coverage.run]
|
|
45
|
+
parallel = true
|
|
46
|
+
concurrency = ["multiprocessing", "thread"]
|
|
47
|
+
omit = [
|
|
48
|
+
"*_test.py",
|
|
49
|
+
"test_*.py",
|
|
50
|
+
"*/tests/*",
|
|
51
|
+
"*/conftest.py",
|
|
52
|
+
"*/utils/testing.py",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[tool.inline-snapshot]
|
|
56
|
+
default-flags=["report"]
|
|
57
|
+
default-flags-tui=["report"]
|
|
58
|
+
default-flags-ide=["report"]
|
|
59
|
+
format-command="uv run ruff format --force-exclude --config pyproject.toml --stdin-filename {filename}"
|
|
60
|
+
|
|
61
|
+
[tool.pyright]
|
|
62
|
+
venvPath = "../.."
|
|
63
|
+
venv = ".venv"
|
|
64
|
+
pythonVersion = "3.12"
|
|
65
|
+
strict = ["**/*.py"]
|
|
66
|
+
|
|
67
|
+
[tool.coverage.report]
|
|
68
|
+
fail_under = 95
|