claude-dev-env 1.31.0 → 1.33.0
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.
- package/hooks/blocking/code_rules_enforcer.py +109 -0
- package/hooks/blocking/test_windows_rmtree_blocker.py +155 -0
- package/hooks/blocking/windows_rmtree_blocker.py +102 -0
- package/hooks/config/hook_log_extractor_constants.py +13 -0
- package/hooks/config/session_env_cleanup_constants.py +20 -0
- package/hooks/config/test_hook_log_extractor_constants.py +27 -0
- package/hooks/config/test_session_env_cleanup_constants.py +60 -0
- package/hooks/diagnostic/hook_log_stop_wrapper.py +107 -19
- package/hooks/diagnostic/test_hook_log_stop_wrapper.py +258 -11
- package/hooks/hooks.json +15 -0
- package/hooks/session/session_env_cleanup.py +130 -0
- package/hooks/session/test_session_env_cleanup.py +280 -0
- package/package.json +1 -1
- package/rules/windows-filesystem-safe.md +91 -0
- package/skills/bugteam/PROMPTS.md +39 -0
- package/skills/bugteam/SKILL.md +49 -1
- package/skills/bugteam/SKILL_EVALS.md +1 -1
- package/skills/bugteam/reference/copilot-gap-analysis.md +496 -0
- package/skills/bugteam/reference/teardown-publish-permissions.md +1 -1
- package/skills/bugteam/scripts/README.md +17 -0
- package/skills/bugteam/scripts/bugteam_code_rules_gate.py +94 -0
- package/skills/bugteam/scripts/bugteam_fix_hookspath.py +260 -0
- package/skills/bugteam/scripts/config/__init__.py +0 -0
- package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +17 -0
- package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +267 -0
- package/skills/logifix/SKILL.md +69 -0
- package/skills/logifix/scripts/logifix.ps1 +205 -0
- package/skills/rebase/SKILL.md +164 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
# Copilot gap analysis
|
|
2
|
+
|
|
3
|
+
This file is the reference record produced by the read-only investigation of why the `/bugteam` audit/fix loop and `bugteam_code_rules_gate.py` repeatedly miss the classes of code-quality violations that the GitHub Copilot reviewer raises on follow-up review rounds. It is written so future bugteam runs can skim the inventory, the rubric/validator coverage diffs, and the patch plan without re-deriving them.
|
|
4
|
+
|
|
5
|
+
Sources of truth cited below: `~/.claude/docs/CODE_RULES.md`, `~/.claude/CLAUDE.md`, `~/.claude/rules/file-global-constants.md`, `~/.claude/skills/bugteam/SKILL.md`, `~/.claude/skills/bugteam/PROMPTS.md`, `~/.claude/skills/bugteam/CONSTRAINTS.md`, `~/.claude/skills/bugteam/scripts/bugteam_code_rules_gate.py`, `~/.claude/skills/bugteam/scripts/bugteam_preflight.py`, `~/.claude/hooks/blocking/code_rules_enforcer.py`, plus `gh api repos/JonEcho/python-automation/pulls/{70,73}/comments` filtered to author `Copilot`.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Investigation report
|
|
10
|
+
|
|
11
|
+
### Copilot finding inventory
|
|
12
|
+
|
|
13
|
+
Copilot review comments fetched with:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
gh api repos/JonEcho/python-automation/pulls/70/comments --paginate --jq '.[] | select(.user.login == "Copilot")'
|
|
17
|
+
gh api repos/JonEcho/python-automation/pulls/73/comments --paginate --jq '.[] | select(.user.login == "Copilot")'
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
PR #70 head SHA `29117309cf4ec1e83883160d8c819e0843f9c3ac` (merged). PR #73 head SHA `c9c935a96cc59d39d623dc7eddda3d341007607c` (merged); Copilot reviewed at original commit `e4abf52c3a6c724b4e64bfed0d979cd60a2c8bf0`.
|
|
21
|
+
|
|
22
|
+
| finding_id | pr_number | file:line | rule_cited | severity | mapped_bugteam_category_letter | layer_that_should_have_caught_it |
|
|
23
|
+
|---|---|---|---|---|---|---|
|
|
24
|
+
| 3153098661 | 70 | `shared_utils/theme_db/writer.py:158` | Magic values — column-name string literals (`"theme_name"`, `"content_id"`, …) hardcoded inside SQL builder bodies (CODE_RULES.md §⚡ Magic values; J) | P1 | J | bugteam pre-flight gate (`bugteam_code_rules_gate.py`) — string literals are masked by `_mask_string_literals_preserving_length`, so the number-only magic-value detector never sees them; bugteam audit rubric (J) names the rule but the regex passes |
|
|
25
|
+
| 3153098689 | 70 | `shared_utils/theme_db/writer.py:361` | File-length / function-length / SRP smell (`write_outcome` >30 lines, module 446 lines exceeds the 400-line advisory) | P2 | none | initial+final standards-review phases bracketing the loop — no rubric letter exists; the existing 400/1000-line advisory in `code_rules_enforcer.advise_file_line_count` is stderr-only and never blocks |
|
|
26
|
+
| 3153098727 | 70 | `shared_utils/theme_db/summary.py:267` | Library `print()` calls in non-CLI library code (CODE_RULES.md §Self-Documenting Code; "Make output stream explicit" practice) | P1 | none | harness PreToolUse hook (`code_rules_enforcer.py`) — no detector exists for `print(`/`sys.stdout.write` inside library modules |
|
|
27
|
+
| 3153098762 | 70 | `shared_utils/theme_db/summary.py:263` | PR-description spec drift — banner missing column-header rows promised in PR body | P2 | A (loosely) | initial+final standards-review phases — A is signature/async-shaped, not "promised behavior vs implementation" shaped |
|
|
28
|
+
| 3153098782 | 70 | `shared_utils/theme_db/writer.py:125` | Naming clarity — `_is_set_column_value` reads like it excludes `None` but does not | P2 | none | bugteam audit rubric addendum — no naming-clarity category in A–J |
|
|
29
|
+
| 3153475246 | 73 | `shared_utils/theme_db/config/constants.py:91` | Collection naming — `THEMES_INSERT_REQUIRED_COLUMN_NAMES` is a tuple and must use the `ALL_*` prefix (CODE_RULES.md §5 "Extended naming rules" → Collections: `all_orders`, `all_users`) | P1 | none | harness PreToolUse hook AND bugteam pre-flight gate — no detector for the collection-prefix rule. Reproduced at `e4abf52c`; renamed to `ALL_THEMES_INSERT_REQUIRED_COLUMN_NAMES` in the merged PR head |
|
|
30
|
+
| 3153475297 | 73 | `shared_utils/theme_db/writer.py:296` | Collection naming — parameter `column_value_pairs` is a list and must use the `all_*` prefix | P1 | none | harness PreToolUse hook AND bugteam pre-flight gate — same gap as 3153475246 for parameter names |
|
|
31
|
+
| 3153475331 | 73 | `shared_utils/theme_db/summary.py:206` (referenced; underlying defect is in `shared_utils/theme_db/tracker.py` `flush()`) | Wrapper plumb-through — public `tracker.flush(*, output_folder)` silently drops `loud_banner_stream` that `ThemeDatabaseWriteSummary.flush(*, output_folder, loud_banner_stream=None)` accepts | P1 | A (loosely) | bugteam audit rubric addendum — A focuses on signatures/return types, not on whether a wrapper preserves the optional kwargs of the function it delegates to |
|
|
32
|
+
|
|
33
|
+
### Rubric coverage diff
|
|
34
|
+
|
|
35
|
+
Source: `~/.claude/skills/bugteam/PROMPTS.md` lines 25-38 ("bug_categories"). Each line below names what the category currently asks the bugfind teammate to do, then lists the Copilot finding ids that fell through it.
|
|
36
|
+
|
|
37
|
+
- **A. API contract verification (signatures, return types, async/await correctness)** — checks signatures and types on the function under audit; does not require the bugfind teammate to compare a public wrapper signature against the inner function it delegates to, and does not cover the human-shaped "the function name is misleading" or "the implementation does not match the PR description" cases. Fell through: 3153098689, 3153098727 (no library-print framing), 3153098762, 3153098782, 3153475331.
|
|
38
|
+
- **B. Selector / query / engine compatibility** — none.
|
|
39
|
+
- **C. Resource cleanup and lifecycle (file handles, connections, processes, locks)** — none.
|
|
40
|
+
- **D. Variable scoping, ordering, and unbound references** — none.
|
|
41
|
+
- **E. Dead code: dead parameters, dead locals, dead imports, dead branches, dead returns, and unused imports** — none.
|
|
42
|
+
- **F. Silent failures (catch-all excepts, unconditional success returns, missing error propagation)** — none.
|
|
43
|
+
- **G. Off-by-one, bounds, and integer overflow** — none.
|
|
44
|
+
- **H. Security boundaries (injection, path traversal, auth bypass, secret leakage)** — none.
|
|
45
|
+
- **I. Concurrency hazards (race conditions, missing awaits, shared mutable state)** — none.
|
|
46
|
+
- **J. Magic values and configuration drift** — names the rule but the bugfind teammate has been observed treating numeric literals as the only magic-value class; string literals that are domain identifiers (column names, status enums, table names) repeatedly slip past. Fell through: 3153098661.
|
|
47
|
+
|
|
48
|
+
Categories absent from A–J entirely: collection prefix (`ALL_*` / `all_*`), library `print()` / direct `sys.stdout.write` in non-CLI code, file-length/function-length/SRP smell, naming-clarity (misleading positive name), wrapper plumb-through of optional kwargs, and PR-description vs implementation drift.
|
|
49
|
+
|
|
50
|
+
### Validator coverage diff
|
|
51
|
+
|
|
52
|
+
Source: every detector inside `~/.claude/hooks/blocking/code_rules_enforcer.py` reused by `~/.claude/skills/bugteam/scripts/bugteam_code_rules_gate.py` via `load_validate_content()` (gate.py:24-40).
|
|
53
|
+
|
|
54
|
+
- `check_comments_python` / `check_comments_javascript` (enforcer.py:103-184) — flag `#` and `//` comments outside exempt markers. Catches none of the eight Copilot findings.
|
|
55
|
+
- `check_comment_changes` (enforcer.py:256-289) — diff-aware comment add/remove. Catches none.
|
|
56
|
+
- `check_imports_at_top` (enforcer.py:292-354) — `import` inside function bodies. Catches none.
|
|
57
|
+
- `check_logging_fstrings` (enforcer.py:357-376) — `log_*(f"...")` and `logger.*(f"...")`. Catches none.
|
|
58
|
+
- `check_windows_api_none` (enforcer.py:402-414) — `win32gui.*(..., None)`. Catches none.
|
|
59
|
+
- `check_magic_values` (enforcer.py:444-491) — number literals inside function bodies, with `_mask_string_literals_preserving_length` (enforcer.py:422-441) blanking string content before the regex runs and `0`, `1`, `-1`, `0.0`, `1.0` allowed. Cannot see string-literal magic values such as `"theme_name"` or `"content_id"`. Misses 3153098661.
|
|
60
|
+
- `check_fstring_structural_literals` (enforcer.py:551-598) — only flags f-strings whose literal portion looks like a path / URL / Windows drive / regex anchor. Plain `("theme_name", theme_name)` tuple entries are not f-strings and are not "structural" by `_has_structural_shape` (enforcer.py:525-548). Misses 3153098661.
|
|
61
|
+
- `check_constants_outside_config` (enforcer.py:735-786) — module-level `UPPER_SNAKE = …` outside `config/`. Files in `config/` are exempt via `is_config_file`, so a constant placed correctly under `config/` is silent. Does not check the `ALL_*` shape on collection-typed constants. Misses 3153475246.
|
|
62
|
+
- `check_constants_outside_config_advisory` (enforcer.py:848-859) — function-local UPPER_SNAKE advisory only. Catches none.
|
|
63
|
+
- `check_file_global_constants_use_count` (enforcer.py:1338-1390) — file-global UPPER_SNAKE used by exactly one caller. Catches none.
|
|
64
|
+
- `check_type_escape_hatches` (enforcer.py:711-726) — `Any` and unjustified `# type: ignore`. Catches none.
|
|
65
|
+
- `check_banned_identifiers` (enforcer.py:908-933) — `result`, `data`, `output`, `response`, `value`, `item`, `temp`. Does not name `column_value_pairs` (it is not a banned word) and does not enforce the inverse "must have a prefix" rule for collections. Misses 3153475297.
|
|
66
|
+
- `check_boolean_naming` (enforcer.py:1032-1064) — boolean assignments require `is_/has_/should_/can_` prefix. Direct analogue of the rule we need but only for booleans. Misses 3153475246, 3153475297.
|
|
67
|
+
- `check_skip_decorators_in_tests` / `check_existence_check_tests` / `check_constant_equality_tests` (enforcer.py:1079-1277) — test-file checks. Catches none.
|
|
68
|
+
- `check_unused_optional_parameters` (enforcer.py:1854-1933) — same-file callers must vary an optional parameter. Does not catch the inverse case where a wrapper drops an underlying function's optional kwarg from its own signature. Misses 3153475331.
|
|
69
|
+
- `check_incomplete_mocks` / `check_duplicated_format_patterns` (enforcer.py:1746-1851) — advisory-only on test files / repeated f-strings. Catches none.
|
|
70
|
+
- `advise_file_line_count` (enforcer.py:379-399) — soft advisory at 400, hard advisory at 1000; never blocking. Triggers on PR #70 `writer.py` (446 lines) but is stderr-only, so the bugteam gate exit code stays 0 and the audit/fix loop never sees the signal as a finding. Misses 3153098689 in practice.
|
|
71
|
+
|
|
72
|
+
`bugteam_code_rules_gate.py` adds no detectors of its own — `run_gate` (gate.py:379-443) only filters violations to the changed-line set and routes blocking/advisory output. Extending the gate without extending `validate_content` (or adding sibling detectors invoked from `run_gate`) cannot close the gap.
|
|
73
|
+
|
|
74
|
+
Validators absent entirely:
|
|
75
|
+
- Collection-prefix `ALL_*` / `all_*` for tuple/list/set/dict assignments and function parameters.
|
|
76
|
+
- Library `print(` / `sys.stdout.write(` / `sys.stderr.write(` outside CLI entry points.
|
|
77
|
+
- String-literal magic values inside function bodies (column names, status enums, table names) when they are not f-string structural shapes.
|
|
78
|
+
- Wrapper plumb-through detector — public function calling a same-file inner function whose signature has optional kwargs absent from the wrapper.
|
|
79
|
+
|
|
80
|
+
### Root-cause statement
|
|
81
|
+
|
|
82
|
+
The bugteam audit rubric (PROMPTS.md §bug_categories A–J) and the deterministic validators (`bugteam_code_rules_gate.py` reusing `code_rules_enforcer.validate_content`) together cover only a narrow slice of `CODE_RULES.md`: number-only magic values, UPPER_SNAKE constants location, boolean naming, banned identifiers, comments, type hints, and a small AST-level f-string check. Three rule classes that `CODE_RULES.md §5 "Extended naming rules"` and the readability rubric explicitly require — the collection prefix `ALL_*` / `all_*`, library `print()` / direct `sys.stdout.write` in non-CLI code, and string-literal magic values that are not structural f-string fragments — have neither a rubric category nor a deterministic detector, so every audit/fix loop converges "0 P0 / 0 P1 / 0 P2 → clean" while leaving them in place; the fourth class — wrapper plumb-through (a public function silently dropping the optional kwargs of an underlying call) — is API-contract-shaped but does not fit category A's signature/async framing. The right enforcement layer is layered: deterministic checks (collection prefix, library print, string-literal magic for known SQL/HTTP keys) belong in `code_rules_enforcer.py` so they block at write time AND in the gate via the existing `validate_content` reuse path; judgment-heavy checks (PR-description drift, naming clarity, SRP/length smells, wrapper plumb-through) belong in a Copilot-derived rubric addendum to `PROMPTS.md`, plus an INITIAL and FINAL standards-review phase bracketing the audit/fix loop so the addendum runs against the cumulative diff with no clean-room context loss.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Patch plan
|
|
87
|
+
|
|
88
|
+
Each section names exactly one target file, the literal text or regex to add, and a verification step that re-runs the new detection against the PR #70 / PR #73 diffs.
|
|
89
|
+
|
|
90
|
+
### a. `~/.claude/skills/bugteam/PROMPTS.md`
|
|
91
|
+
|
|
92
|
+
**Insertion site:** the `<bug_categories>` block inside the AUDIT spawn-prompt XML (PROMPTS.md lines 25-38). Append four new categories K–N and a "Copilot-derived addendum" preamble immediately before the closing `</bug_categories>` tag, leaving A–J unchanged.
|
|
93
|
+
|
|
94
|
+
**New section header:** `Copilot-derived addendum (K–N)`
|
|
95
|
+
|
|
96
|
+
**Literal text to add:**
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
Copilot-derived addendum (K–N) — verify each one explicitly. Return at
|
|
100
|
+
least one finding per category OR a verified-clean entry that names the
|
|
101
|
+
exact files and lines you walked.
|
|
102
|
+
K. Collection naming. Every tuple, list, set, dict, mapping, or sequence
|
|
103
|
+
parameter must follow the CODE_RULES.md §5 "Extended naming rules"
|
|
104
|
+
prefix discipline:
|
|
105
|
+
- module-level constant whose value is a tuple/list/set/dict/frozenset
|
|
106
|
+
literal MUST start with `ALL_` (e.g. `ALL_THEMES_INSERT_REQUIRED_COLUMN_NAMES`)
|
|
107
|
+
- function/method parameter whose annotation is `list[...]`, `tuple[...]`,
|
|
108
|
+
`set[...]`, `dict[...]`, `Iterable[...]`, `Sequence[...]`, `Mapping[...]`,
|
|
109
|
+
or `frozenset[...]` MUST start with `all_` (e.g. `all_column_value_pairs`)
|
|
110
|
+
- exempt: dict/map names that follow the `X_by_Y` pattern (e.g.
|
|
111
|
+
`price_by_product`)
|
|
112
|
+
L. Library print / direct stdout. In any module that is not a CLI entry
|
|
113
|
+
point (`__main__`, `*_cli.py`, `scripts/*.py`), every `print(...)`,
|
|
114
|
+
`sys.stdout.write(...)`, `sys.stderr.write(...)` call is a finding.
|
|
115
|
+
The fix is to route through a `logger` call OR to make the output
|
|
116
|
+
stream an explicit parameter so callers can redirect it.
|
|
117
|
+
M. String-literal magic values. Treat domain-identifier string literals
|
|
118
|
+
(database column names, table names, HTTP header names, status enums,
|
|
119
|
+
environment-variable names) inside a function body as magic values
|
|
120
|
+
even when the existing number-only check would let them pass. The
|
|
121
|
+
fix is to extract them into `config/` and reference the imported
|
|
122
|
+
name. Do not flag plain log messages, error messages, or one-off
|
|
123
|
+
human-readable strings.
|
|
124
|
+
N. Wrapper plumb-through. When a public function delegates to an
|
|
125
|
+
inner function defined in the same package, every optional kwarg
|
|
126
|
+
accepted by the inner function MUST appear in the public wrapper
|
|
127
|
+
unless the wrapper docstring explicitly states the kwarg is fixed
|
|
128
|
+
to a sentinel default. Silently dropping `loud_banner_stream`,
|
|
129
|
+
`timeout`, `dry_run`, or any similar optional kwarg is a finding.
|
|
130
|
+
</bug_categories>
|
|
131
|
+
|
|
132
|
+
<copilot_derived_addendum_source>
|
|
133
|
+
The K–N categories were added after Copilot raised real findings on
|
|
134
|
+
PR #70 (writer.py / summary.py) and PR #73 (constants.py / writer.py /
|
|
135
|
+
tracker.py) that converged "0 P0 / 0 P1 / 0 P2" under the original
|
|
136
|
+
A–J rubric. See ~/.claude/skills/bugteam/reference/copilot-gap-analysis.md
|
|
137
|
+
for the inventory and the validators that now back categories K and L.
|
|
138
|
+
</copilot_derived_addendum_source>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
(Replace the existing closing `</bug_categories>` line with the literal text above so K–N live inside the same block as A–J.)
|
|
142
|
+
|
|
143
|
+
**Verification step (one line, no `$(...)`):**
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
python C:/Users/jon/.claude/skills/bugteam/scripts/bugteam_code_rules_gate.py /tmp/pr70_writer.py /tmp/pr70_summary.py /tmp/pr73_constants.py /tmp/pr73_writer.py
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Run after the K and L deterministic detectors land in §c/d below; the categories M and N stay rubric-only and are exercised by replaying PR #70 / PR #73 through `/bugteam` with the new PROMPTS.md and observing that the audit posts findings keyed to lines 158 (M), 125 (rubric N — naming clarity), 263 (rubric N — PR-description drift), 361 (initial/final standards review — file length), and 206 (N — wrapper plumb-through).
|
|
150
|
+
|
|
151
|
+
### b. `~/.claude/skills/bugteam/SKILL.md`
|
|
152
|
+
|
|
153
|
+
**Insertion site 1 — progress checklist (SKILL.md lines 72-81).** Add two rows so the checklist reads:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
[ ] Step 0: project permissions granted
|
|
157
|
+
[ ] Step 1: PR scope resolved
|
|
158
|
+
[ ] Step 2: agent team created + loop state set
|
|
159
|
+
[ ] Step 2.6: INITIAL standards review against cumulative PR diff
|
|
160
|
+
[ ] Step 3: cycle complete (converged | cap reached | stuck | error)
|
|
161
|
+
[ ] Step 3.5: FINAL standards review against cumulative PR diff
|
|
162
|
+
[ ] Step 4: team torn down + working tree clean
|
|
163
|
+
[ ] Step 4.5: PR description rewritten (or skip warning logged)
|
|
164
|
+
[ ] Step 5: project permissions revoked
|
|
165
|
+
[ ] Step 6: final report printed
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Insertion site 2 — between the existing Step 2.5 ("PR comments") and Step 3 ("The cycle").** Add a new section:
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
### Step 2.6: INITIAL standards review (once, before Loop 1 audit)
|
|
172
|
+
|
|
173
|
+
Run BEFORE the first pre-audit gate fires. Spawn a fresh `code-quality-agent`
|
|
174
|
+
teammate inside the same team and drive it through the K–N addendum (see
|
|
175
|
+
PROMPTS.md `<copilot_derived_addendum_source>`). The teammate audits the
|
|
176
|
+
cumulative PR diff (`gh pr diff <N>`) instead of a single loop's incremental
|
|
177
|
+
patch; clean-room context is preserved by the same agent-team isolation as
|
|
178
|
+
the per-loop bugfind teammate. Findings are posted using the same Step 2.5
|
|
179
|
+
review-shape with body `## /bugteam INITIAL standards review against PR #<N>
|
|
180
|
+
cumulative diff: <P0>P0 / <P1>P1 / <P2>P2`. Findings advance the audit/fix
|
|
181
|
+
cycle exactly as if they had been raised in Loop 1: the lead increments
|
|
182
|
+
`loop_count` to 1, sets `last_action = "audited"` with the merged
|
|
183
|
+
`last_findings`, and Step 3 begins on the FIX branch. When the INITIAL
|
|
184
|
+
review returns zero findings, `loop_count` stays at 0 and Step 3 begins on
|
|
185
|
+
the AUDIT branch as before. Failure on this phase logs the error and
|
|
186
|
+
proceeds to Step 3 unchanged so the legacy A–J cycle still runs.
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Insertion site 3 — between the existing Step 3 cycle exit and Step 4 ("Teardown").** Add a new section:
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
### Step 3.5: FINAL standards review (once, after convergence)
|
|
193
|
+
|
|
194
|
+
Run AFTER Step 3 exits with `converged`, `cap reached`, or `stuck`, and
|
|
195
|
+
BEFORE Step 4 teardown. Spawn one more fresh `code-quality-agent` teammate;
|
|
196
|
+
audit the cumulative PR diff against the K–N addendum a second time. Post
|
|
197
|
+
the review with body `## /bugteam FINAL standards review against PR #<N>
|
|
198
|
+
cumulative diff: <P0>P0 / <P1>P1 / <P2>P2`. When findings remain, the
|
|
199
|
+
exit reason is upgraded to `error: final standards review found <P0>+<P1>+<P2>
|
|
200
|
+
unresolved finding(s)` and the loop log gains an extra `final-review` line.
|
|
201
|
+
A clean FINAL review preserves the existing exit reason. Failure on this
|
|
202
|
+
phase logs the error and continues to Step 4 unchanged so teardown,
|
|
203
|
+
permission revoke, and the final report still run.
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Insertion site 4 — Step 6 final report template (SKILL.md lines 308-320).** Extend the loop log section so both new phases appear:
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
Loop log:
|
|
210
|
+
initial standards review: 1P0 0P1 2P2
|
|
211
|
+
1 audit: 3P0 2P1 0P2
|
|
212
|
+
...
|
|
213
|
+
final standards review: 0P0 0P1 0P2
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Verification step:**
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
gh pr diff 70 -R JonEcho/python-automation > /tmp/pr70.diff && gh pr diff 73 -R JonEcho/python-automation > /tmp/pr73.diff && /bugteam --dry-run --replay 70 73
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Followed by reading `<team_temp_dir>/pr-70/initial-review.outcomes.xml` and `final-review.outcomes.xml` to confirm Copilot finding ids 3153098661, 3153098689, 3153098727, 3153098762, 3153098782, 3153475246, 3153475297, 3153475331 are surfaced as new entries in either the INITIAL or FINAL review (or both) and that `Step 3.5` upgrades the exit reason when any P0/P1 finding remains.
|
|
223
|
+
|
|
224
|
+
### c. `~/.claude/skills/bugteam/scripts/bugteam_code_rules_gate.py`
|
|
225
|
+
|
|
226
|
+
**Insertion site:** new module-level helper functions immediately after `is_code_path` (gate.py:237-239), and a new top-level call inside `run_gate` (gate.py:379-443) that augments `validate_content`'s output. The new detectors live in this file (not in `code_rules_enforcer.py`) when their false-positive rate is too high for write-time blocking but still acceptable for the bugteam gate's coarser granularity. The collection-prefix and library-print detectors below are also added to `code_rules_enforcer.py` (§d) so they reach Write/Edit; the column-name string-literal detector below is bugteam-only.
|
|
227
|
+
|
|
228
|
+
**Detector 1 — column-name string magic values (rubric M deterministic backstop):**
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
def check_database_column_string_magic(content: str, file_path: str) -> list[str]:
|
|
232
|
+
"""Flag string literals that look like database/HTTP column or key names inside function bodies.
|
|
233
|
+
|
|
234
|
+
Triggers when a string literal matches the snake_case shape used by SQL
|
|
235
|
+
column names and table identifiers and appears as a tuple element, list
|
|
236
|
+
element, dict key, dict value, or function-call argument inside a
|
|
237
|
+
function body. Files under ``config/`` and test files are exempt.
|
|
238
|
+
"""
|
|
239
|
+
if "/config/" in file_path.replace("\\", "/") or "\\config\\" in file_path:
|
|
240
|
+
return []
|
|
241
|
+
if "/tests/" in file_path.replace("\\", "/") or file_path.endswith(("_test.py", ".spec.py")):
|
|
242
|
+
return []
|
|
243
|
+
issues: list[str] = []
|
|
244
|
+
column_name_shape = re.compile(r'"([a-z][a-z0-9_]{2,})"|\'([a-z][a-z0-9_]{2,})\'')
|
|
245
|
+
inside_function = False
|
|
246
|
+
function_def_pattern = re.compile(r"^\s*(async\s+)?def\s+\w+")
|
|
247
|
+
class_def_pattern = re.compile(r"^\s*class\s+\w+")
|
|
248
|
+
builder_context_pattern = re.compile(r"\b(column|columns|fields|keys|select|insert|update|where|table)\b", re.IGNORECASE)
|
|
249
|
+
for line_number, each_line in enumerate(content.splitlines(), 1):
|
|
250
|
+
if function_def_pattern.match(each_line):
|
|
251
|
+
inside_function = True
|
|
252
|
+
continue
|
|
253
|
+
if class_def_pattern.match(each_line):
|
|
254
|
+
inside_function = False
|
|
255
|
+
continue
|
|
256
|
+
if not inside_function:
|
|
257
|
+
continue
|
|
258
|
+
if not builder_context_pattern.search(each_line):
|
|
259
|
+
continue
|
|
260
|
+
for first_quote_match, second_quote_match in column_name_shape.findall(each_line):
|
|
261
|
+
literal_text = first_quote_match or second_quote_match
|
|
262
|
+
if not literal_text:
|
|
263
|
+
continue
|
|
264
|
+
if literal_text in {"true", "false", "none", "null"}:
|
|
265
|
+
continue
|
|
266
|
+
issues.append(
|
|
267
|
+
f"Line {line_number}: Column-name string magic {literal_text!r} - extract to config"
|
|
268
|
+
)
|
|
269
|
+
if len(issues) >= 3:
|
|
270
|
+
return issues
|
|
271
|
+
return issues
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Wired into `run_gate` (replace the body of the per-file loop in gate.py:387-414 so the new detector's output joins `issues`):
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
issues = validate_content(content, str(relative).replace("\\", "/"), old_content=content)
|
|
278
|
+
issues.extend(check_database_column_string_magic(content, str(relative).replace("\\", "/")))
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Detector 2 — wrapper plumb-through (rubric N deterministic backstop, file-local only):**
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
def check_wrapper_plumb_through(content: str, file_path: str) -> list[str]:
|
|
285
|
+
"""Flag public wrappers that drop optional kwargs of a same-file delegate.
|
|
286
|
+
|
|
287
|
+
Walks the AST. For every public function (name does not start with '_'),
|
|
288
|
+
if its body contains exactly one direct call to another same-file
|
|
289
|
+
function and that delegate's signature accepts optional kwargs that the
|
|
290
|
+
wrapper does not also accept, emit a finding with both line numbers.
|
|
291
|
+
"""
|
|
292
|
+
if file_path.endswith((".js", ".ts", ".tsx", ".jsx")):
|
|
293
|
+
return []
|
|
294
|
+
try:
|
|
295
|
+
tree = ast.parse(content)
|
|
296
|
+
except SyntaxError:
|
|
297
|
+
return []
|
|
298
|
+
function_signatures: dict[str, set[str]] = {}
|
|
299
|
+
for node in ast.walk(tree):
|
|
300
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
301
|
+
optional_kwargs: set[str] = set()
|
|
302
|
+
for each_kwonly, each_default in zip(node.args.kwonlyargs, node.args.kw_defaults):
|
|
303
|
+
if each_default is not None:
|
|
304
|
+
optional_kwargs.add(each_kwonly.arg)
|
|
305
|
+
function_signatures[node.name] = optional_kwargs
|
|
306
|
+
issues: list[str] = []
|
|
307
|
+
for node in ast.walk(tree):
|
|
308
|
+
if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
309
|
+
continue
|
|
310
|
+
if node.name.startswith("_"):
|
|
311
|
+
continue
|
|
312
|
+
wrapper_kwargs = function_signatures.get(node.name, set())
|
|
313
|
+
for each_call in ast.walk(node):
|
|
314
|
+
if not isinstance(each_call, ast.Call):
|
|
315
|
+
continue
|
|
316
|
+
if not isinstance(each_call.func, ast.Attribute):
|
|
317
|
+
continue
|
|
318
|
+
delegate_name = each_call.func.attr
|
|
319
|
+
delegate_kwargs = function_signatures.get(delegate_name)
|
|
320
|
+
if delegate_kwargs is None:
|
|
321
|
+
continue
|
|
322
|
+
missing = delegate_kwargs - wrapper_kwargs
|
|
323
|
+
if missing:
|
|
324
|
+
issues.append(
|
|
325
|
+
f"Line {node.lineno}: Wrapper {node.name!r} drops optional kwargs {sorted(missing)!r} of delegate {delegate_name!r}"
|
|
326
|
+
)
|
|
327
|
+
if len(issues) >= 3:
|
|
328
|
+
return issues
|
|
329
|
+
return issues
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Wire into `run_gate` the same way as Detector 1, by appending its output to `issues` immediately after the column-magic call.
|
|
333
|
+
|
|
334
|
+
**Imports:** add `import ast` to the top of `bugteam_code_rules_gate.py` (currently absent).
|
|
335
|
+
|
|
336
|
+
**Verification step:**
|
|
337
|
+
|
|
338
|
+
```
|
|
339
|
+
python C:/Users/jon/.claude/skills/bugteam/scripts/bugteam_code_rules_gate.py /tmp/pr70_writer.py /tmp/pr70_summary.py /tmp/pr73_constants.py /tmp/pr73_writer.py /tmp/pr73_tracker.py
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Expected output after the patch lands: at minimum one `Column-name string magic 'theme_name' - extract to config` line on `pr70_writer.py` (Copilot id 3153098661) and one `Wrapper 'flush' drops optional kwargs ['loud_banner_stream'] of delegate 'flush'` line on `pr73_tracker.py` (Copilot id 3153475331).
|
|
343
|
+
|
|
344
|
+
### d. `~/.claude/hooks/blocking/code_rules_enforcer.py`
|
|
345
|
+
|
|
346
|
+
The root-cause statement names write-time enforcement as the right layer for the collection-prefix and library-print rules, because both have a low false-positive rate and produce concrete, mechanical fixes. Both detectors plug into `validate_content` (enforcer.py:1936-1978) so they reach Write/Edit (the harness PreToolUse path) and the bugteam gate (which reuses `validate_content` via `load_validate_content`).
|
|
347
|
+
|
|
348
|
+
**Detector 1 — collection-prefix (rubric K deterministic backstop):**
|
|
349
|
+
|
|
350
|
+
```python
|
|
351
|
+
COLLECTION_TYPE_NAMES: frozenset[str] = frozenset({
|
|
352
|
+
"list", "tuple", "set", "frozenset", "dict",
|
|
353
|
+
"Iterable", "Sequence", "Mapping", "MutableMapping", "FrozenSet",
|
|
354
|
+
})
|
|
355
|
+
COLLECTION_BY_NAME_PATTERN: re.Pattern[str] = re.compile(r"^[a-z][a-z0-9]*_by_[a-z][a-z0-9_]*$")
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _annotation_names_collection(annotation_node: ast.expr | None) -> bool:
|
|
359
|
+
if annotation_node is None:
|
|
360
|
+
return False
|
|
361
|
+
if isinstance(annotation_node, ast.Name):
|
|
362
|
+
return annotation_node.id in COLLECTION_TYPE_NAMES
|
|
363
|
+
if isinstance(annotation_node, ast.Subscript):
|
|
364
|
+
return _annotation_names_collection(annotation_node.value)
|
|
365
|
+
if isinstance(annotation_node, ast.Attribute):
|
|
366
|
+
return annotation_node.attr in COLLECTION_TYPE_NAMES
|
|
367
|
+
return False
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def check_collection_prefix(content: str, file_path: str) -> list[str]:
|
|
371
|
+
if is_test_file(file_path):
|
|
372
|
+
return []
|
|
373
|
+
if is_workflow_registry_file(file_path) or is_migration_file(file_path):
|
|
374
|
+
return []
|
|
375
|
+
try:
|
|
376
|
+
tree = ast.parse(content)
|
|
377
|
+
except SyntaxError:
|
|
378
|
+
return []
|
|
379
|
+
issues: list[str] = []
|
|
380
|
+
for node in tree.body:
|
|
381
|
+
target_name: str | None = None
|
|
382
|
+
target_line = 0
|
|
383
|
+
is_collection_value = False
|
|
384
|
+
if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
|
|
385
|
+
target_name = node.target.id
|
|
386
|
+
target_line = node.lineno
|
|
387
|
+
is_collection_value = _annotation_names_collection(node.annotation)
|
|
388
|
+
elif isinstance(node, ast.Assign) and len(node.targets) == 1 and isinstance(node.targets[0], ast.Name):
|
|
389
|
+
target_name = node.targets[0].id
|
|
390
|
+
target_line = node.lineno
|
|
391
|
+
is_collection_value = isinstance(node.value, (ast.Tuple, ast.List, ast.Set, ast.Dict))
|
|
392
|
+
if target_name is None or not is_collection_value:
|
|
393
|
+
continue
|
|
394
|
+
if not UPPER_SNAKE_CONSTANT_PATTERN.match(target_name):
|
|
395
|
+
continue
|
|
396
|
+
if target_name.startswith("ALL_") or COLLECTION_BY_NAME_PATTERN.match(target_name.lower()):
|
|
397
|
+
continue
|
|
398
|
+
issues.append(
|
|
399
|
+
f"Line {target_line}: Collection constant {target_name} - prefix with ALL_ (CODE_RULES §5)"
|
|
400
|
+
)
|
|
401
|
+
if len(issues) >= MAX_ISSUES_PER_CHECK:
|
|
402
|
+
break
|
|
403
|
+
for node in ast.walk(tree):
|
|
404
|
+
if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
405
|
+
continue
|
|
406
|
+
for each_arg in _collect_annotated_arguments(node):
|
|
407
|
+
if not _annotation_names_collection(each_arg.annotation):
|
|
408
|
+
continue
|
|
409
|
+
if each_arg.arg in {"self", "cls"}:
|
|
410
|
+
continue
|
|
411
|
+
if each_arg.arg.startswith("all_") or COLLECTION_BY_NAME_PATTERN.match(each_arg.arg):
|
|
412
|
+
continue
|
|
413
|
+
issues.append(
|
|
414
|
+
f"Line {each_arg.lineno}: Collection parameter {each_arg.arg} - prefix with all_ (CODE_RULES §5)"
|
|
415
|
+
)
|
|
416
|
+
if len(issues) >= MAX_ISSUES_PER_CHECK:
|
|
417
|
+
return issues
|
|
418
|
+
return issues
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**File-path filter:** Python files only (`extension in PYTHON_EXTENSIONS`); `is_test_file` / `is_config_file` / `is_workflow_registry_file` / `is_migration_file` exempt families.
|
|
422
|
+
|
|
423
|
+
**Corrective error message:** `Line N: Collection constant FOO - prefix with ALL_ (CODE_RULES §5)` and `Line N: Collection parameter foo - prefix with all_ (CODE_RULES §5)`.
|
|
424
|
+
|
|
425
|
+
**Detector 2 — library print (rubric L deterministic backstop):**
|
|
426
|
+
|
|
427
|
+
```python
|
|
428
|
+
CLI_FILE_PATH_MARKERS: tuple[str, ...] = ("/scripts/", "\\scripts\\", "_cli.py", "/cli.py", "\\cli.py")
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def _is_cli_entry_point(file_path: str) -> bool:
|
|
432
|
+
path_lower = file_path.lower().replace("\\", "/")
|
|
433
|
+
return any(marker.replace("\\", "/") in path_lower for marker in CLI_FILE_PATH_MARKERS)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def check_library_print(content: str, file_path: str) -> list[str]:
|
|
437
|
+
if is_test_file(file_path) or is_config_file(file_path) or is_hook_infrastructure(file_path):
|
|
438
|
+
return []
|
|
439
|
+
if _is_cli_entry_point(file_path):
|
|
440
|
+
return []
|
|
441
|
+
if get_file_extension(file_path) not in PYTHON_EXTENSIONS:
|
|
442
|
+
return []
|
|
443
|
+
try:
|
|
444
|
+
tree = ast.parse(content)
|
|
445
|
+
except SyntaxError:
|
|
446
|
+
return []
|
|
447
|
+
issues: list[str] = []
|
|
448
|
+
for node in ast.walk(tree):
|
|
449
|
+
if not isinstance(node, ast.Call):
|
|
450
|
+
continue
|
|
451
|
+
function_reference = node.func
|
|
452
|
+
if isinstance(function_reference, ast.Name) and function_reference.id == "print":
|
|
453
|
+
issues.append(
|
|
454
|
+
f"Line {node.lineno}: Library print() - route through logger or accept an explicit stream parameter"
|
|
455
|
+
)
|
|
456
|
+
elif isinstance(function_reference, ast.Attribute) and function_reference.attr == "write":
|
|
457
|
+
value_node = function_reference.value
|
|
458
|
+
if isinstance(value_node, ast.Attribute) and isinstance(value_node.value, ast.Name):
|
|
459
|
+
if value_node.value.id == "sys" and value_node.attr in {"stdout", "stderr"}:
|
|
460
|
+
issues.append(
|
|
461
|
+
f"Line {node.lineno}: sys.{value_node.attr}.write - route through logger"
|
|
462
|
+
)
|
|
463
|
+
if len(issues) >= MAX_ISSUES_PER_CHECK:
|
|
464
|
+
break
|
|
465
|
+
return issues
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
**File-path filter:** Python only; CLI entry points (`/scripts/`, `*_cli.py`, `cli.py`), hook infrastructure, config files, and test files exempt.
|
|
469
|
+
|
|
470
|
+
**Corrective error message:** `Line N: Library print() - route through logger or accept an explicit stream parameter` (and the parallel `sys.stdout.write` / `sys.stderr.write` form).
|
|
471
|
+
|
|
472
|
+
**Wire-up in `validate_content`** — append two new lines inside the `if extension in PYTHON_EXTENSIONS:` block of `validate_content` (enforcer.py:1948-1968), immediately after `check_unused_optional_parameters`:
|
|
473
|
+
|
|
474
|
+
```python
|
|
475
|
+
all_issues.extend(check_collection_prefix(content, file_path))
|
|
476
|
+
all_issues.extend(check_library_print(content, file_path))
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
**Verification step:**
|
|
480
|
+
|
|
481
|
+
```
|
|
482
|
+
python -c "import importlib.util, sys; spec=importlib.util.spec_from_file_location('e','C:/Users/jon/.claude/hooks/blocking/code_rules_enforcer.py'); m=importlib.util.module_from_spec(spec); spec.loader.exec_module(m); content=open('/tmp/pr73_constants.py').read(); print(m.check_collection_prefix(content,'shared_utils/theme_db/config/constants.py'))"
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Expected output after the patch lands: a list containing `Line 91: Collection constant THEMES_INSERT_REQUIRED_COLUMN_NAMES - prefix with ALL_ (CODE_RULES §5)`. The same command against `/tmp/pr73_writer.py` (Copilot id 3153475297) emits `Line 296: Collection parameter column_value_pairs - prefix with all_ (CODE_RULES §5)`. Replacing the call with `m.check_library_print` against `/tmp/pr70_summary.py` (Copilot id 3153098727) emits at minimum one `Line 256: Library print() - …` line.
|
|
486
|
+
|
|
487
|
+
**Justification for touching `code_rules_enforcer.py`:** the root-cause statement names write-time enforcement as the right layer for collection-prefix and library-print, because both rules in `CODE_RULES.md §5` are mechanical (no judgment), produce concrete fixes, and were the dominant source of follow-up Copilot findings (3 of 8 inventory rows). The bugteam pre-flight gate alone is insufficient — it only fires before each AUDIT, so a clean-coder fix pass that introduces a new violation lives unblocked until the next gate run; write-time enforcement closes that window.
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
## Cross-references
|
|
492
|
+
|
|
493
|
+
- Inventory data sources: live `gh api repos/JonEcho/python-automation/pulls/{70,73}/comments` filtered to `Copilot`; verbatim bodies preserved in the inventory table above.
|
|
494
|
+
- Original-commit content used to confirm violations: PR #70 head `29117309cf4ec1e83883160d8c819e0843f9c3ac`; PR #73 review-time commit `e4abf52c3a6c724b4e64bfed0d979cd60a2c8bf0`; PR #73 merged head `c9c935a96cc59d39d623dc7eddda3d341007607c`.
|
|
495
|
+
- CODE_RULES.md sections invoked by the patch plan: §⚡ Magic values; §5 Extended naming rules (collections `all_orders`, `all_users`); §6.5 File length guidance; §7 Right-Sized Engineering; §10 No redundant data fetches (used as analogue for wrapper plumb-through).
|
|
496
|
+
- Constraints honored: `gh-body-file` (no `gh ... --body` calls in the new code paths), `no-shell-substitution` (no `$(...)` in the verification commands above; multi-step shell flows are written as separate Bash invocations or `&&`-chained literal strings).
|
|
@@ -24,7 +24,7 @@ When the cycle exits (any reason), run these steps in order from **this** sessio
|
|
|
24
24
|
|
|
25
25
|
2. **Clean up the team** with `TeamDelete()` (no arguments — reads `<team_name>` from session context). Maps to “clean up the team” in the docs; quote: [`../sources.md`](../sources.md).
|
|
26
26
|
|
|
27
|
-
3. **Delete the per-team temp directory** using the Python one-liner in `SKILL.md` with the same literal `<team_temp_dir>` from Step 2. `
|
|
27
|
+
3. **Delete the per-team temp directory** using the Python one-liner in `SKILL.md` with the same literal `<team_temp_dir>` from Step 2. The one-liner uses an `onexc`/`onerror` handler that strips the Windows ReadOnly attribute and retries the failing syscall — `ignore_errors=True` is unsafe on Windows because it silently swallows ReadOnly-attribute failures (see `~/.claude/rules/windows-filesystem-safe.md`).
|
|
28
28
|
|
|
29
29
|
## Step 4.5 — Finalize the PR description (mandatory)
|
|
30
30
|
|
|
@@ -5,6 +5,7 @@ Scripts in this directory are **executed** by the lead or teammates. They are no
|
|
|
5
5
|
| Script | Purpose |
|
|
6
6
|
|--------|---------|
|
|
7
7
|
| `bugteam_preflight.py` | Run pytest (when configured) and optional `pre-commit` before `/bugteam`. |
|
|
8
|
+
| `bugteam_fix_hookspath.py` | Auto-remediate a stale local `core.hooksPath` override, set canonical global value, re-run `bugteam_preflight.py`. Invoked by Claude when preflight reports a `core.hooksPath` failure. |
|
|
8
9
|
| `bugteam_code_rules_gate.py` | Run `validate_content` from `code-rules-enforcer.py` on PR-scoped files (`git diff` vs merge-base). Exit `1` if any mandatory rule fails. Invoked **before each audit**; the fixer clears it before the auditor runs. |
|
|
9
10
|
| `grant_project_claude_permissions.py` | Idempotent grant of Edit/Write/Read on `cwd/.claude/**` into `~/.claude/settings.json`. |
|
|
10
11
|
| `revoke_project_claude_permissions.py` | Removes the matching grant entries from `~/.claude/settings.json`. |
|
|
@@ -24,6 +25,22 @@ python "${CLAUDE_SKILL_DIR}/scripts/bugteam_preflight.py"
|
|
|
24
25
|
- Pytest exit code `5` (no tests collected) is treated as success.
|
|
25
26
|
- Add `--pre-commit` to run `pre-commit run --all-files` when `.pre-commit-config.yaml` exists.
|
|
26
27
|
|
|
28
|
+
## `bugteam_fix_hookspath.py`
|
|
29
|
+
|
|
30
|
+
From the repository root:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
python "${CLAUDE_SKILL_DIR}/scripts/bugteam_fix_hookspath.py"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- Removes any local-scope `core.hooksPath` value that does not end in `hooks/git-hooks`.
|
|
37
|
+
- Sets `git config --global core.hooksPath ~/.claude/hooks/git-hooks` when the global value is unset or non-canonical.
|
|
38
|
+
- Refuses to run (exit non-zero) when `~/.claude/hooks/git-hooks` does not exist on disk — install via `npx claude-dev-env .` first.
|
|
39
|
+
- Idempotent: a second invocation is a clean no-op.
|
|
40
|
+
- Re-runs `bugteam_preflight.py --no-pytest` and propagates its exit code.
|
|
41
|
+
|
|
42
|
+
The bugteam SKILL invokes this automatically when preflight stderr indicates a `core.hooksPath` failure, so Claude does not surface the error to the user.
|
|
43
|
+
|
|
27
44
|
## `bugteam_code_rules_gate.py`
|
|
28
45
|
|
|
29
46
|
From the repository root (same merge-base rules as the PR head vs base — default `--base origin/main`):
|