bugyi-chops 0.7.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.
- bugyi_chops-0.7.0/.gitignore +8 -0
- bugyi_chops-0.7.0/LICENSE +21 -0
- bugyi_chops-0.7.0/PKG-INFO +317 -0
- bugyi_chops-0.7.0/README.md +288 -0
- bugyi_chops-0.7.0/pyproject.toml +93 -0
- bugyi_chops-0.7.0/src/bugyi_chops/__init__.py +5 -0
- bugyi_chops-0.7.0/src/bugyi_chops/_common.py +168 -0
- bugyi_chops-0.7.0/src/bugyi_chops/_report.py +72 -0
- bugyi_chops-0.7.0/src/bugyi_chops/ci_watch.py +2061 -0
- bugyi_chops-0.7.0/src/bugyi_chops/py.typed +0 -0
- bugyi_chops-0.7.0/src/bugyi_chops/toobig_split.py +572 -0
- bugyi_chops-0.7.0/tests/conftest.py +58 -0
- bugyi_chops-0.7.0/tests/test_ci_watch.py +1632 -0
- bugyi_chops-0.7.0/tests/test_common_helpers.py +174 -0
- bugyi_chops-0.7.0/tests/test_toobig_split.py +1300 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bryan Bugyi
|
|
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.
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bugyi-chops
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: Personal SASE axe chops for notifications, releases, and maintenance proposals
|
|
5
|
+
Project-URL: Homepage, https://github.com/bbugyi200/bugyi-chops
|
|
6
|
+
Project-URL: Repository, https://github.com/bbugyi200/bugyi-chops
|
|
7
|
+
Project-URL: Issues, https://github.com/bbugyi200/bugyi-chops/issues
|
|
8
|
+
Author: Bryan Bugyi
|
|
9
|
+
Maintainer: Bryan Bugyi
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: automation,coding agents,developer tools,sase
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: POSIX
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.12
|
|
26
|
+
Requires-Dist: sase<0.17.0,>=0.16.0
|
|
27
|
+
Requires-Dist: toobig<0.2.0,>=0.1.0
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# bugyi-chops
|
|
31
|
+
|
|
32
|
+
`bugyi-chops` is Bryan Bugyi's community [SASE](https://sase.sh/) plugin for
|
|
33
|
+
scheduled Axe jobs that watch repository health, submit guarded release PRs, and
|
|
34
|
+
propose maintenance work. It supplies two console scripts:
|
|
35
|
+
|
|
36
|
+
| Script | Responsibility |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `bugyi_chop_ci_watch` | Notify on CI failures and squash-merge explicitly enabled, guarded release-please PRs |
|
|
39
|
+
| `bugyi_chop_toobig_split` | One sequential condition-gated `%auto #split_file:<path>` agent per oversized Python file, routed through `@medium` |
|
|
40
|
+
|
|
41
|
+
The scripts never launch agents themselves. `bugyi_chop_toobig_split` scans and
|
|
42
|
+
assembles prompts, then uses the public `sase.chops` SDK to atomically write a
|
|
43
|
+
validated result document; Axe owns guard and trigger evaluation, keyed-proposal
|
|
44
|
+
deduplication, workspace allocation, proposal launches, and the final action lifecycle
|
|
45
|
+
for those proposals.
|
|
46
|
+
|
|
47
|
+
`bugyi_chop_ci_watch` is deliberately narrower. It never creates gates, launch
|
|
48
|
+
requests, repair prompts, or Axe proposals. Its only mutation outside its own state and
|
|
49
|
+
report files is a guarded `gh pr merge --squash --match-head-commit` for an eligible
|
|
50
|
+
release-please PR when `vars.merge_enabled` is true and `SASE_CHOP_DRY_RUN=0` is
|
|
51
|
+
explicitly present. Missing or true dry-run context suppresses merging and
|
|
52
|
+
notifications.
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
Install the published package into the same managed environment as SASE:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
sase plugin install bugyi-chops
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`-g`/`--git` forces a built VCS snapshot install from this repository instead of the
|
|
63
|
+
published index distribution; it is not a development install. For local development
|
|
64
|
+
against this repository, see [Development and releases](#development-and-releases)
|
|
65
|
+
below.
|
|
66
|
+
|
|
67
|
+
All scripts require Python 3.12 or newer and `sase>=0.16.0,<0.17`. SASE 0.16.0 is the
|
|
68
|
+
first compatible release series for typed Axe chop admission with `%if`. The package
|
|
69
|
+
also installs the `toobig` scanner used by `bugyi_chop_toobig_split`.
|
|
70
|
+
|
|
71
|
+
## The chop result contract
|
|
72
|
+
|
|
73
|
+
Axe invokes a configured script as `<script> --context <context.json>` and supplies
|
|
74
|
+
`SASE_CHOP_RESULT_FILE`. A successful script writes schema-versioned JSON like:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"schema_version": 1,
|
|
79
|
+
"status": "ok",
|
|
80
|
+
"summary": "toobig_split: files=1 proposals=1 skipped=0",
|
|
81
|
+
"counters": {"files": 1, "proposals": 1, "skipped": 0},
|
|
82
|
+
"proposed_launches": [
|
|
83
|
+
{
|
|
84
|
+
"id": "split_file-src-large_py",
|
|
85
|
+
"prompt": "%if::\n```bash\npath=src/large.py\n...\n```\n%auto #split_file:src/large.py",
|
|
86
|
+
"workspace": "gh:sase-org/sase",
|
|
87
|
+
"model": "@medium"
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`status` is `ok`, `no_op`, or `check_error`. Axe validates the entire result before it
|
|
94
|
+
launches anything, injects the workspace/name/tribe scaffold, honors `wait_on`
|
|
95
|
+
dependencies, filters duplicate proposals, and tracks linked agents through
|
|
96
|
+
`action_succeeded` or `action_failed`. The prompts here may use inline xprompts such
|
|
97
|
+
as `#pr` and `#split_file`; they never use forbidden standalone `#!workflow`
|
|
98
|
+
references. This proposal contract governs `bugyi_chop_toobig_split`.
|
|
99
|
+
`bugyi_chop_ci_watch` always returns an empty `proposed_launches` list.
|
|
100
|
+
|
|
101
|
+
## `ci_watch`
|
|
102
|
+
|
|
103
|
+
`bugyi_chop_ci_watch` sweeps the configured repository allowlist through `actstat`,
|
|
104
|
+
reconciles each settled commit with the current default branch through bounded GitHub
|
|
105
|
+
queries, and publishes a combined `CI WATCH` report. Red observations keep typed job
|
|
106
|
+
evidence: workflow name, job name, conclusion, direct job/run URL, failing steps,
|
|
107
|
+
failing SHA, and current-head SHA when a stale settled failure is still actionable
|
|
108
|
+
while newer HEAD checks are unsettled.
|
|
109
|
+
|
|
110
|
+
Failure notifications are incident-based. The state file `ci_watch_state.json` tracks
|
|
111
|
+
the active failing-job fingerprint per repository, independent of SHA. The same
|
|
112
|
+
failure is announced once and kept active while it persists; changed job/step evidence
|
|
113
|
+
replaces the incident; a green or non-red observation clears it so a later recurrence
|
|
114
|
+
is announced again. Failed notification deliveries remain marked unsent and are retried
|
|
115
|
+
on the next live tick.
|
|
116
|
+
|
|
117
|
+
Release handling is release-please only. Configure `vars.release_repositories` as a
|
|
118
|
+
list of repositories from `vars.repos`; generator mappings are rejected and other
|
|
119
|
+
release branch families are ignored. A release PR is mergeable only when all guards
|
|
120
|
+
pass: exactly one release-please candidate for the repository, configured default base, non-draft,
|
|
121
|
+
mergeable and clean, non-empty fully green check rollup, green current default branch,
|
|
122
|
+
idle release-please/publish workflow, deterministic dependency order,
|
|
123
|
+
`max_merges_per_tick`, explicit live mode, a final PR/default-branch reread, and
|
|
124
|
+
`--match-head-commit` race protection.
|
|
125
|
+
|
|
126
|
+
Successful merge submissions are written durably with `notification_sent: false` before
|
|
127
|
+
any SASE notification is attempted. The release notification is marked delivered only
|
|
128
|
+
after `sase notify create` succeeds, so a later tick retries an unsent release notice.
|
|
129
|
+
If report publication fails, inline notifications still include the full evidence but
|
|
130
|
+
omit the `ViewReport` action and the chop returns `check_error` so the failure is
|
|
131
|
+
visible.
|
|
132
|
+
|
|
133
|
+
Minimal configuration:
|
|
134
|
+
|
|
135
|
+
```yaml
|
|
136
|
+
vars:
|
|
137
|
+
actstat_bin: /home/bryan/.cargo/bin/actstat
|
|
138
|
+
gh_bin: gh
|
|
139
|
+
sase_bin: sase
|
|
140
|
+
repos: [sase-org/sase, sase-org/sase-core, sase-org/sase-telegram]
|
|
141
|
+
release_repositories: [sase-org/sase-telegram, sase-org/sase]
|
|
142
|
+
merge_order: [sase-telegram, sase]
|
|
143
|
+
max_merges_per_tick: 1
|
|
144
|
+
merge_enabled: true
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## `toobig_split`
|
|
148
|
+
|
|
149
|
+
`bugyi_chop_toobig_split` runs `toobig --files-only` for each configured tree,
|
|
150
|
+
normalizes and de-duplicates its paths, and emits one proposal per file.
|
|
151
|
+
|
|
152
|
+
The scanner contract is fail-closed around two healthy outcomes. Exit `0` is a
|
|
153
|
+
completed scan whose stdout listing, if any, contains only informational or
|
|
154
|
+
warning-level paths. Exit `1` with one or more listed paths is also a completed
|
|
155
|
+
scan: `toobig` uses that exit code for a hard-limit hit and still writes the
|
|
156
|
+
matching paths to stdout, which this chop consumes as actionable findings.
|
|
157
|
+
Malformed invocations, missing trees, and other filesystem errors reuse exit
|
|
158
|
+
`1` but produce no path payload; those empty exit-`1` results, and every other
|
|
159
|
+
nonzero status, remain a typed `check_error`.
|
|
160
|
+
|
|
161
|
+
Each proposal has:
|
|
162
|
+
|
|
163
|
+
- the shared `toobig-@` clan template, with a keyed basename member template
|
|
164
|
+
(`<basename>.{@<path-digest>}`);
|
|
165
|
+
- the same Rich `clan_summary` metadata, describing the mission, file and scan-root
|
|
166
|
+
counts, configured limits, and sequential queue;
|
|
167
|
+
- structured `model: "@medium"`, which Axe renders as one `%model:@medium`
|
|
168
|
+
directive alongside `%auto #split_file:<path>`;
|
|
169
|
+
- one `%if::` Bash fence followed by `%auto #split_file:<path>` as its prompt;
|
|
170
|
+
- no proposal `dedupe_key`; every scheduled scan may reconsider files that remain
|
|
171
|
+
oversized;
|
|
172
|
+
- `wait_on` pointing to the prior file, preserving sequential workspace allocation.
|
|
173
|
+
|
|
174
|
+
The `%if` fence runs after the proposal's sequential wait and immediately before typed
|
|
175
|
+
admission. SASE briefly claims a temporary numbered workspace to evaluate it — never a
|
|
176
|
+
runner, agent identity, or model request — and releases that claim once the verdict is
|
|
177
|
+
settled, whether the proposal is eligible or skipped. This is why a queued condition
|
|
178
|
+
still sees a file an earlier agent already split even when the chop's own checkout is
|
|
179
|
+
stale: the leased checkout synchronizes from the configured upstream before `%if` runs.
|
|
180
|
+
The proposal skips when the target file is gone or has dropped below the configured
|
|
181
|
+
floor (`min(limits)`, 700 lines in the default configuration) in that freshly leased
|
|
182
|
+
checkout. A read/count failure for an existing file is a visible condition error. This
|
|
183
|
+
requires SASE's `typed_launch_units` flag and a compatible
|
|
184
|
+
SASE 0.16.x runtime; older SASE 0.13.x runtimes reject these directives before model
|
|
185
|
+
dispatch.
|
|
186
|
+
|
|
187
|
+
The structured `@medium` field is the only model source. It selects SASE's
|
|
188
|
+
configurable, load-balanced alias pool rather than pinning a concrete
|
|
189
|
+
provider/model in the prompt body, so operators retune `@medium` centrally
|
|
190
|
+
without a per-chop model variable.
|
|
191
|
+
|
|
192
|
+
The repeated summary metadata is deliberate: after conditional admission or any other
|
|
193
|
+
proposal filtering, any surviving proposal can safely become the clan declarer. Axe
|
|
194
|
+
allocates the concrete clan template once per actionable scan and emits the summary
|
|
195
|
+
exactly once on that declaration; clan joiners do not redeclare it. In ACE the default
|
|
196
|
+
scan reads as:
|
|
197
|
+
|
|
198
|
+
```text
|
|
199
|
+
◆ TOOBIG SPLIT · 3 FILES
|
|
200
|
+
MISSION
|
|
201
|
+
Decompose oversized Python modules into focused, reviewable units
|
|
202
|
+
without changing behavior.
|
|
203
|
+
2 scan roots · limits 1,000 / 850 / 700 lines · sequential queue
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Concrete agent names look like `toobig-<token>.<basename>.<token>`, for example
|
|
207
|
+
`toobig-3j.test_query_profile.0`. Two files that share a basename keep the same
|
|
208
|
+
readable stem and allocate distinct member tokens (`.0`, `.1`) inside that clan.
|
|
209
|
+
All proposals from that scan belong to the same clan generation, while later scans
|
|
210
|
+
can allocate a new one. The plugin authors only the `toobig-@` clan template, a
|
|
211
|
+
keyed basename member template, and proposal metadata; it never inspects live
|
|
212
|
+
agents or chooses concrete tokens. Axe alone allocates the clan and member
|
|
213
|
+
tokens, injects declaration/join directives, and launches agents.
|
|
214
|
+
|
|
215
|
+
The script deliberately has no flock, no `sase agent list`, and no `sase run`. Those
|
|
216
|
+
responsibilities now belong to Axe:
|
|
217
|
+
|
|
218
|
+
```yaml
|
|
219
|
+
axe:
|
|
220
|
+
lumberjacks:
|
|
221
|
+
maintenance:
|
|
222
|
+
description: |-
|
|
223
|
+
Propose split-file maintenance for oversized Python files once a minute
|
|
224
|
+
|
|
225
|
+
Runs every 60 seconds so the chop can notice when its hourly `run_every` window opens. Use this lane for
|
|
226
|
+
low-cost maintenance proposals guarded by AXE, not for commit-threshold audits.
|
|
227
|
+
|
|
228
|
+
The `toobig-` clan inhibit guard prevents a new split-file scan while an earlier split swarm is still active.
|
|
229
|
+
interval: 60
|
|
230
|
+
chops:
|
|
231
|
+
toobig_split:
|
|
232
|
+
script: bugyi_chop_toobig_split
|
|
233
|
+
description: |-
|
|
234
|
+
Split oversized Python files in sase
|
|
235
|
+
|
|
236
|
+
Runs `bugyi_chop_toobig_split` once per hour for the `sase` target and scans the configured `src` and
|
|
237
|
+
`tests` trees with the `1000`, `850`, and `700` line limits.
|
|
238
|
+
|
|
239
|
+
Each actionable scan emits sequential `toobig-` clan proposals. Every member rechecks the configured
|
|
240
|
+
700-line floor through `%if` after its wait and before admission, so stale files skip without allocating an
|
|
241
|
+
agent while still-oversized files launch normally. The chop sets the structured proposal model to `@medium`;
|
|
242
|
+
Axe emits `%model:@medium` and SASE consumes the alias pool at each real invocation.
|
|
243
|
+
run_every: 60m
|
|
244
|
+
inhibit_if:
|
|
245
|
+
agent_clan: {name_prefix: toobig-}
|
|
246
|
+
for_each:
|
|
247
|
+
source: projects
|
|
248
|
+
names: [sase]
|
|
249
|
+
vars:
|
|
250
|
+
trees: [src, tests]
|
|
251
|
+
limits: [1000, 850, 700]
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
The projects target source supplies both `target.workspace` and
|
|
255
|
+
`target.workspace_dir`. For a literal target, provide the same fields. Compatibility
|
|
256
|
+
environment variables are also accepted: `SASE_TOOBIG_SPLIT_PROJECT`,
|
|
257
|
+
`SASE_TOOBIG_SPLIT_REPO_ROOT`, `SASE_TOOBIG_SPLIT_LAUNCH_REF`,
|
|
258
|
+
`SASE_TOOBIG_SPLIT_TREES`, `SASE_TOOBIG_SPLIT_LIMITS`, and
|
|
259
|
+
`SASE_TOOBIG_SPLIT_TOOBIG`.
|
|
260
|
+
|
|
261
|
+
## Debugging
|
|
262
|
+
|
|
263
|
+
Preview configured chops without side effects:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
sase axe chop run 'ci_watch' -L ci_watch --dry-run --chop-verbose
|
|
267
|
+
sase axe chop run 'toobig_split[sase]' -L maintenance --dry-run --chop-verbose
|
|
268
|
+
# Short flags:
|
|
269
|
+
sase axe chop run 'ci_watch' -L ci_watch -n -V
|
|
270
|
+
sase axe chop run 'toobig_split[sase]' -L maintenance -n -V
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`-V` sets `SASE_CHOP_VERBOSE` and prints scanner commands and target diagnostics.
|
|
274
|
+
Every actual invocation also emits a compact summary line with bounded integer
|
|
275
|
+
counters and an explicit reason for no-op/error outcomes.
|
|
276
|
+
|
|
277
|
+
For lower-level diagnosis, reuse a context JSON written by Axe and invoke a script
|
|
278
|
+
directly. `ci_watch` requires `SASE_CHOP_DRY_RUN=0` before it can notify or submit a
|
|
279
|
+
merge; all other direct invocations render decisions only:
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
SASE_CHOP_RESULT_FILE=/tmp/ci-watch-result.json \
|
|
283
|
+
bugyi_chop_ci_watch --context /path/to/context.json --verbose
|
|
284
|
+
jq . /tmp/ci-watch-result.json
|
|
285
|
+
|
|
286
|
+
SASE_CHOP_RESULT_FILE=/tmp/toobig-result.json \
|
|
287
|
+
bugyi_chop_toobig_split --context /path/to/context.json --verbose
|
|
288
|
+
jq . /tmp/toobig-result.json
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Development and releases
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
just install
|
|
295
|
+
just check
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
When developing before the matching SASE release is available on PyPI, first run
|
|
299
|
+
`just install` in a current SASE source checkout (with its linked `sase-core`), then
|
|
300
|
+
reuse that environment:
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
BUGYI_CHOPS_VENV_BIN=/path/to/sase/.venv/bin just install
|
|
304
|
+
BUGYI_CHOPS_VENV_BIN=/path/to/sase/.venv/bin just check
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
`just check` runs formatting/lint/type checks, pytest with branch coverage, builds the
|
|
308
|
+
wheel and source distribution, and validates both artifacts with Twine. Pull requests
|
|
309
|
+
and pushes to `master` run the same checks on Python 3.12 and 3.13.
|
|
310
|
+
|
|
311
|
+
Releases are tag-driven. Set the package version, push the matching `v<version>` tag,
|
|
312
|
+
and the publish workflow rebuilds and tests the package before uploading to PyPI via
|
|
313
|
+
GitHub trusted publishing. No long-lived PyPI token is stored in the repository.
|
|
314
|
+
|
|
315
|
+
## License
|
|
316
|
+
|
|
317
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# bugyi-chops
|
|
2
|
+
|
|
3
|
+
`bugyi-chops` is Bryan Bugyi's community [SASE](https://sase.sh/) plugin for
|
|
4
|
+
scheduled Axe jobs that watch repository health, submit guarded release PRs, and
|
|
5
|
+
propose maintenance work. It supplies two console scripts:
|
|
6
|
+
|
|
7
|
+
| Script | Responsibility |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `bugyi_chop_ci_watch` | Notify on CI failures and squash-merge explicitly enabled, guarded release-please PRs |
|
|
10
|
+
| `bugyi_chop_toobig_split` | One sequential condition-gated `%auto #split_file:<path>` agent per oversized Python file, routed through `@medium` |
|
|
11
|
+
|
|
12
|
+
The scripts never launch agents themselves. `bugyi_chop_toobig_split` scans and
|
|
13
|
+
assembles prompts, then uses the public `sase.chops` SDK to atomically write a
|
|
14
|
+
validated result document; Axe owns guard and trigger evaluation, keyed-proposal
|
|
15
|
+
deduplication, workspace allocation, proposal launches, and the final action lifecycle
|
|
16
|
+
for those proposals.
|
|
17
|
+
|
|
18
|
+
`bugyi_chop_ci_watch` is deliberately narrower. It never creates gates, launch
|
|
19
|
+
requests, repair prompts, or Axe proposals. Its only mutation outside its own state and
|
|
20
|
+
report files is a guarded `gh pr merge --squash --match-head-commit` for an eligible
|
|
21
|
+
release-please PR when `vars.merge_enabled` is true and `SASE_CHOP_DRY_RUN=0` is
|
|
22
|
+
explicitly present. Missing or true dry-run context suppresses merging and
|
|
23
|
+
notifications.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
Install the published package into the same managed environment as SASE:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
sase plugin install bugyi-chops
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`-g`/`--git` forces a built VCS snapshot install from this repository instead of the
|
|
34
|
+
published index distribution; it is not a development install. For local development
|
|
35
|
+
against this repository, see [Development and releases](#development-and-releases)
|
|
36
|
+
below.
|
|
37
|
+
|
|
38
|
+
All scripts require Python 3.12 or newer and `sase>=0.16.0,<0.17`. SASE 0.16.0 is the
|
|
39
|
+
first compatible release series for typed Axe chop admission with `%if`. The package
|
|
40
|
+
also installs the `toobig` scanner used by `bugyi_chop_toobig_split`.
|
|
41
|
+
|
|
42
|
+
## The chop result contract
|
|
43
|
+
|
|
44
|
+
Axe invokes a configured script as `<script> --context <context.json>` and supplies
|
|
45
|
+
`SASE_CHOP_RESULT_FILE`. A successful script writes schema-versioned JSON like:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"schema_version": 1,
|
|
50
|
+
"status": "ok",
|
|
51
|
+
"summary": "toobig_split: files=1 proposals=1 skipped=0",
|
|
52
|
+
"counters": {"files": 1, "proposals": 1, "skipped": 0},
|
|
53
|
+
"proposed_launches": [
|
|
54
|
+
{
|
|
55
|
+
"id": "split_file-src-large_py",
|
|
56
|
+
"prompt": "%if::\n```bash\npath=src/large.py\n...\n```\n%auto #split_file:src/large.py",
|
|
57
|
+
"workspace": "gh:sase-org/sase",
|
|
58
|
+
"model": "@medium"
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`status` is `ok`, `no_op`, or `check_error`. Axe validates the entire result before it
|
|
65
|
+
launches anything, injects the workspace/name/tribe scaffold, honors `wait_on`
|
|
66
|
+
dependencies, filters duplicate proposals, and tracks linked agents through
|
|
67
|
+
`action_succeeded` or `action_failed`. The prompts here may use inline xprompts such
|
|
68
|
+
as `#pr` and `#split_file`; they never use forbidden standalone `#!workflow`
|
|
69
|
+
references. This proposal contract governs `bugyi_chop_toobig_split`.
|
|
70
|
+
`bugyi_chop_ci_watch` always returns an empty `proposed_launches` list.
|
|
71
|
+
|
|
72
|
+
## `ci_watch`
|
|
73
|
+
|
|
74
|
+
`bugyi_chop_ci_watch` sweeps the configured repository allowlist through `actstat`,
|
|
75
|
+
reconciles each settled commit with the current default branch through bounded GitHub
|
|
76
|
+
queries, and publishes a combined `CI WATCH` report. Red observations keep typed job
|
|
77
|
+
evidence: workflow name, job name, conclusion, direct job/run URL, failing steps,
|
|
78
|
+
failing SHA, and current-head SHA when a stale settled failure is still actionable
|
|
79
|
+
while newer HEAD checks are unsettled.
|
|
80
|
+
|
|
81
|
+
Failure notifications are incident-based. The state file `ci_watch_state.json` tracks
|
|
82
|
+
the active failing-job fingerprint per repository, independent of SHA. The same
|
|
83
|
+
failure is announced once and kept active while it persists; changed job/step evidence
|
|
84
|
+
replaces the incident; a green or non-red observation clears it so a later recurrence
|
|
85
|
+
is announced again. Failed notification deliveries remain marked unsent and are retried
|
|
86
|
+
on the next live tick.
|
|
87
|
+
|
|
88
|
+
Release handling is release-please only. Configure `vars.release_repositories` as a
|
|
89
|
+
list of repositories from `vars.repos`; generator mappings are rejected and other
|
|
90
|
+
release branch families are ignored. A release PR is mergeable only when all guards
|
|
91
|
+
pass: exactly one release-please candidate for the repository, configured default base, non-draft,
|
|
92
|
+
mergeable and clean, non-empty fully green check rollup, green current default branch,
|
|
93
|
+
idle release-please/publish workflow, deterministic dependency order,
|
|
94
|
+
`max_merges_per_tick`, explicit live mode, a final PR/default-branch reread, and
|
|
95
|
+
`--match-head-commit` race protection.
|
|
96
|
+
|
|
97
|
+
Successful merge submissions are written durably with `notification_sent: false` before
|
|
98
|
+
any SASE notification is attempted. The release notification is marked delivered only
|
|
99
|
+
after `sase notify create` succeeds, so a later tick retries an unsent release notice.
|
|
100
|
+
If report publication fails, inline notifications still include the full evidence but
|
|
101
|
+
omit the `ViewReport` action and the chop returns `check_error` so the failure is
|
|
102
|
+
visible.
|
|
103
|
+
|
|
104
|
+
Minimal configuration:
|
|
105
|
+
|
|
106
|
+
```yaml
|
|
107
|
+
vars:
|
|
108
|
+
actstat_bin: /home/bryan/.cargo/bin/actstat
|
|
109
|
+
gh_bin: gh
|
|
110
|
+
sase_bin: sase
|
|
111
|
+
repos: [sase-org/sase, sase-org/sase-core, sase-org/sase-telegram]
|
|
112
|
+
release_repositories: [sase-org/sase-telegram, sase-org/sase]
|
|
113
|
+
merge_order: [sase-telegram, sase]
|
|
114
|
+
max_merges_per_tick: 1
|
|
115
|
+
merge_enabled: true
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## `toobig_split`
|
|
119
|
+
|
|
120
|
+
`bugyi_chop_toobig_split` runs `toobig --files-only` for each configured tree,
|
|
121
|
+
normalizes and de-duplicates its paths, and emits one proposal per file.
|
|
122
|
+
|
|
123
|
+
The scanner contract is fail-closed around two healthy outcomes. Exit `0` is a
|
|
124
|
+
completed scan whose stdout listing, if any, contains only informational or
|
|
125
|
+
warning-level paths. Exit `1` with one or more listed paths is also a completed
|
|
126
|
+
scan: `toobig` uses that exit code for a hard-limit hit and still writes the
|
|
127
|
+
matching paths to stdout, which this chop consumes as actionable findings.
|
|
128
|
+
Malformed invocations, missing trees, and other filesystem errors reuse exit
|
|
129
|
+
`1` but produce no path payload; those empty exit-`1` results, and every other
|
|
130
|
+
nonzero status, remain a typed `check_error`.
|
|
131
|
+
|
|
132
|
+
Each proposal has:
|
|
133
|
+
|
|
134
|
+
- the shared `toobig-@` clan template, with a keyed basename member template
|
|
135
|
+
(`<basename>.{@<path-digest>}`);
|
|
136
|
+
- the same Rich `clan_summary` metadata, describing the mission, file and scan-root
|
|
137
|
+
counts, configured limits, and sequential queue;
|
|
138
|
+
- structured `model: "@medium"`, which Axe renders as one `%model:@medium`
|
|
139
|
+
directive alongside `%auto #split_file:<path>`;
|
|
140
|
+
- one `%if::` Bash fence followed by `%auto #split_file:<path>` as its prompt;
|
|
141
|
+
- no proposal `dedupe_key`; every scheduled scan may reconsider files that remain
|
|
142
|
+
oversized;
|
|
143
|
+
- `wait_on` pointing to the prior file, preserving sequential workspace allocation.
|
|
144
|
+
|
|
145
|
+
The `%if` fence runs after the proposal's sequential wait and immediately before typed
|
|
146
|
+
admission. SASE briefly claims a temporary numbered workspace to evaluate it — never a
|
|
147
|
+
runner, agent identity, or model request — and releases that claim once the verdict is
|
|
148
|
+
settled, whether the proposal is eligible or skipped. This is why a queued condition
|
|
149
|
+
still sees a file an earlier agent already split even when the chop's own checkout is
|
|
150
|
+
stale: the leased checkout synchronizes from the configured upstream before `%if` runs.
|
|
151
|
+
The proposal skips when the target file is gone or has dropped below the configured
|
|
152
|
+
floor (`min(limits)`, 700 lines in the default configuration) in that freshly leased
|
|
153
|
+
checkout. A read/count failure for an existing file is a visible condition error. This
|
|
154
|
+
requires SASE's `typed_launch_units` flag and a compatible
|
|
155
|
+
SASE 0.16.x runtime; older SASE 0.13.x runtimes reject these directives before model
|
|
156
|
+
dispatch.
|
|
157
|
+
|
|
158
|
+
The structured `@medium` field is the only model source. It selects SASE's
|
|
159
|
+
configurable, load-balanced alias pool rather than pinning a concrete
|
|
160
|
+
provider/model in the prompt body, so operators retune `@medium` centrally
|
|
161
|
+
without a per-chop model variable.
|
|
162
|
+
|
|
163
|
+
The repeated summary metadata is deliberate: after conditional admission or any other
|
|
164
|
+
proposal filtering, any surviving proposal can safely become the clan declarer. Axe
|
|
165
|
+
allocates the concrete clan template once per actionable scan and emits the summary
|
|
166
|
+
exactly once on that declaration; clan joiners do not redeclare it. In ACE the default
|
|
167
|
+
scan reads as:
|
|
168
|
+
|
|
169
|
+
```text
|
|
170
|
+
◆ TOOBIG SPLIT · 3 FILES
|
|
171
|
+
MISSION
|
|
172
|
+
Decompose oversized Python modules into focused, reviewable units
|
|
173
|
+
without changing behavior.
|
|
174
|
+
2 scan roots · limits 1,000 / 850 / 700 lines · sequential queue
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Concrete agent names look like `toobig-<token>.<basename>.<token>`, for example
|
|
178
|
+
`toobig-3j.test_query_profile.0`. Two files that share a basename keep the same
|
|
179
|
+
readable stem and allocate distinct member tokens (`.0`, `.1`) inside that clan.
|
|
180
|
+
All proposals from that scan belong to the same clan generation, while later scans
|
|
181
|
+
can allocate a new one. The plugin authors only the `toobig-@` clan template, a
|
|
182
|
+
keyed basename member template, and proposal metadata; it never inspects live
|
|
183
|
+
agents or chooses concrete tokens. Axe alone allocates the clan and member
|
|
184
|
+
tokens, injects declaration/join directives, and launches agents.
|
|
185
|
+
|
|
186
|
+
The script deliberately has no flock, no `sase agent list`, and no `sase run`. Those
|
|
187
|
+
responsibilities now belong to Axe:
|
|
188
|
+
|
|
189
|
+
```yaml
|
|
190
|
+
axe:
|
|
191
|
+
lumberjacks:
|
|
192
|
+
maintenance:
|
|
193
|
+
description: |-
|
|
194
|
+
Propose split-file maintenance for oversized Python files once a minute
|
|
195
|
+
|
|
196
|
+
Runs every 60 seconds so the chop can notice when its hourly `run_every` window opens. Use this lane for
|
|
197
|
+
low-cost maintenance proposals guarded by AXE, not for commit-threshold audits.
|
|
198
|
+
|
|
199
|
+
The `toobig-` clan inhibit guard prevents a new split-file scan while an earlier split swarm is still active.
|
|
200
|
+
interval: 60
|
|
201
|
+
chops:
|
|
202
|
+
toobig_split:
|
|
203
|
+
script: bugyi_chop_toobig_split
|
|
204
|
+
description: |-
|
|
205
|
+
Split oversized Python files in sase
|
|
206
|
+
|
|
207
|
+
Runs `bugyi_chop_toobig_split` once per hour for the `sase` target and scans the configured `src` and
|
|
208
|
+
`tests` trees with the `1000`, `850`, and `700` line limits.
|
|
209
|
+
|
|
210
|
+
Each actionable scan emits sequential `toobig-` clan proposals. Every member rechecks the configured
|
|
211
|
+
700-line floor through `%if` after its wait and before admission, so stale files skip without allocating an
|
|
212
|
+
agent while still-oversized files launch normally. The chop sets the structured proposal model to `@medium`;
|
|
213
|
+
Axe emits `%model:@medium` and SASE consumes the alias pool at each real invocation.
|
|
214
|
+
run_every: 60m
|
|
215
|
+
inhibit_if:
|
|
216
|
+
agent_clan: {name_prefix: toobig-}
|
|
217
|
+
for_each:
|
|
218
|
+
source: projects
|
|
219
|
+
names: [sase]
|
|
220
|
+
vars:
|
|
221
|
+
trees: [src, tests]
|
|
222
|
+
limits: [1000, 850, 700]
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The projects target source supplies both `target.workspace` and
|
|
226
|
+
`target.workspace_dir`. For a literal target, provide the same fields. Compatibility
|
|
227
|
+
environment variables are also accepted: `SASE_TOOBIG_SPLIT_PROJECT`,
|
|
228
|
+
`SASE_TOOBIG_SPLIT_REPO_ROOT`, `SASE_TOOBIG_SPLIT_LAUNCH_REF`,
|
|
229
|
+
`SASE_TOOBIG_SPLIT_TREES`, `SASE_TOOBIG_SPLIT_LIMITS`, and
|
|
230
|
+
`SASE_TOOBIG_SPLIT_TOOBIG`.
|
|
231
|
+
|
|
232
|
+
## Debugging
|
|
233
|
+
|
|
234
|
+
Preview configured chops without side effects:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
sase axe chop run 'ci_watch' -L ci_watch --dry-run --chop-verbose
|
|
238
|
+
sase axe chop run 'toobig_split[sase]' -L maintenance --dry-run --chop-verbose
|
|
239
|
+
# Short flags:
|
|
240
|
+
sase axe chop run 'ci_watch' -L ci_watch -n -V
|
|
241
|
+
sase axe chop run 'toobig_split[sase]' -L maintenance -n -V
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`-V` sets `SASE_CHOP_VERBOSE` and prints scanner commands and target diagnostics.
|
|
245
|
+
Every actual invocation also emits a compact summary line with bounded integer
|
|
246
|
+
counters and an explicit reason for no-op/error outcomes.
|
|
247
|
+
|
|
248
|
+
For lower-level diagnosis, reuse a context JSON written by Axe and invoke a script
|
|
249
|
+
directly. `ci_watch` requires `SASE_CHOP_DRY_RUN=0` before it can notify or submit a
|
|
250
|
+
merge; all other direct invocations render decisions only:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
SASE_CHOP_RESULT_FILE=/tmp/ci-watch-result.json \
|
|
254
|
+
bugyi_chop_ci_watch --context /path/to/context.json --verbose
|
|
255
|
+
jq . /tmp/ci-watch-result.json
|
|
256
|
+
|
|
257
|
+
SASE_CHOP_RESULT_FILE=/tmp/toobig-result.json \
|
|
258
|
+
bugyi_chop_toobig_split --context /path/to/context.json --verbose
|
|
259
|
+
jq . /tmp/toobig-result.json
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Development and releases
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
just install
|
|
266
|
+
just check
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
When developing before the matching SASE release is available on PyPI, first run
|
|
270
|
+
`just install` in a current SASE source checkout (with its linked `sase-core`), then
|
|
271
|
+
reuse that environment:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
BUGYI_CHOPS_VENV_BIN=/path/to/sase/.venv/bin just install
|
|
275
|
+
BUGYI_CHOPS_VENV_BIN=/path/to/sase/.venv/bin just check
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
`just check` runs formatting/lint/type checks, pytest with branch coverage, builds the
|
|
279
|
+
wheel and source distribution, and validates both artifacts with Twine. Pull requests
|
|
280
|
+
and pushes to `master` run the same checks on Python 3.12 and 3.13.
|
|
281
|
+
|
|
282
|
+
Releases are tag-driven. Set the package version, push the matching `v<version>` tag,
|
|
283
|
+
and the publish workflow rebuilds and tests the package before uploading to PyPI via
|
|
284
|
+
GitHub trusted publishing. No long-lived PyPI token is stored in the repository.
|
|
285
|
+
|
|
286
|
+
## License
|
|
287
|
+
|
|
288
|
+
[MIT](LICENSE)
|