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.
@@ -0,0 +1,8 @@
1
+ .coverage
2
+ .deps/
3
+ .mypy_cache/
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .venv/
7
+ __pycache__/
8
+ dist/
@@ -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)