claude-dev-env 1.82.0 → 1.84.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/CLAUDE.md +16 -13
- package/_shared/pr-loop/audit-contract.md +24 -12
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/README.md +1 -0
- package/_shared/pr-loop/scripts/_claude_permissions_common.py +16 -5
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviewer_availability_constants.py +12 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +0 -2
- package/_shared/pr-loop/scripts/reviewer_availability.py +182 -0
- package/_shared/pr-loop/scripts/reviews_disabled.py +2 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +9 -33
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +2 -0
- package/_shared/pr-loop/scripts/tests/test__claude_permissions_common.py +212 -0
- package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +18 -0
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +18 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +37 -0
- package/_shared/pr-loop/scripts/tests/test_reviewer_availability.py +159 -0
- package/_shared/pr-loop/scripts/tests/test_reviewer_availability_constants.py +36 -0
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +14 -0
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +14 -4
- package/agents/clean-coder.md +3 -0
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
- package/bin/install.mjs +16 -8
- package/bin/install.test.mjs +17 -6
- package/commands/CLAUDE.md +0 -1
- package/docs/CODE_RULES.md +1 -1
- package/hooks/blocking/CLAUDE.md +2 -4
- package/hooks/blocking/code_rules_constants_config.py +164 -1
- package/hooks/blocking/code_rules_docstrings.py +428 -15
- package/hooks/blocking/code_rules_enforcer.py +33 -0
- package/hooks/blocking/code_rules_imports_logging.py +867 -1
- package/hooks/blocking/code_rules_magic_values.py +5 -0
- package/hooks/blocking/code_rules_naming_collection.py +152 -6
- package/hooks/blocking/code_rules_shared.py +34 -0
- package/hooks/blocking/code_rules_string_magic.py +68 -0
- package/hooks/blocking/duplicate_rmtree_helper_blocker.py +4 -4
- package/hooks/blocking/pre_tool_use_dispatcher.py +3 -3
- package/hooks/blocking/reviewer_spawn_gate.py +182 -0
- package/hooks/blocking/stale_comment_reference_blocker.py +267 -0
- package/hooks/blocking/state_description_blocker.py +96 -5
- package/hooks/blocking/test_code_rules_config_duplicate_path_anchor.py +132 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +2 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_delegation_summary.py +385 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_prose_wall_illustration.py +197 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_runon_sentence.py +27 -0
- package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +67 -0
- package/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py +40 -0
- package/hooks/blocking/test_code_rules_enforcer_naive_datetime.py +213 -0
- package/hooks/blocking/test_code_rules_enforcer_referenced_underscore_loop.py +169 -0
- package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +33 -0
- package/hooks/blocking/test_code_rules_enforcer_split_shared.py +23 -0
- package/hooks/blocking/test_code_rules_js_bare_flag_return_directive.py +266 -0
- package/hooks/blocking/test_code_rules_js_sibling_return_object_key_drift.py +490 -0
- package/hooks/blocking/test_code_rules_logging_adjacent_literals.py +171 -0
- package/hooks/blocking/test_code_rules_magic_values.py +54 -0
- package/hooks/blocking/test_duplicate_rmtree_helper_blocker.py +0 -6
- package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +184 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +9 -3
- package/hooks/blocking/test_reviewer_spawn_gate.py +230 -0
- package/hooks/blocking/test_shared_stdin_adoption.py +5 -30
- package/hooks/blocking/test_stale_comment_reference_blocker.py +236 -0
- package/hooks/blocking/test_state_description_blocker.py +135 -0
- package/hooks/hooks.json +5 -0
- package/hooks/hooks_constants/CLAUDE.md +3 -4
- package/hooks/hooks_constants/blocking_check_limits.py +46 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +41 -0
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
- package/hooks/hooks_constants/duplicate_rmtree_helper_blocker_constants.py +0 -1
- package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +0 -5
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
- package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +41 -0
- package/hooks/hooks_constants/stale_comment_reference_blocker_constants.py +76 -0
- package/hooks/hooks_constants/state_description_blocker_constants.py +8 -0
- package/hooks/validation/post_tool_use_dispatcher.py +1 -1
- package/hooks/validation/test_post_tool_use_dispatcher.py +51 -35
- package/hooks/workflow/CLAUDE.md +2 -8
- package/package.json +1 -1
- package/rules/CLAUDE.md +5 -2
- package/rules/claude-md-orphan-file.md +5 -0
- package/rules/docstring-prose-matches-implementation.md +10 -1
- package/rules/env-var-table-code-drift.md +5 -0
- package/rules/es-exe-file-search.md +17 -0
- package/rules/no-historical-clutter.md +12 -1
- package/rules/orphan-css-class.md +5 -0
- package/rules/package-inventory-stale-entry.md +10 -0
- package/rules/paired-test-coverage.md +5 -0
- package/rules/plain-illustrative-docstrings.md +40 -1
- package/rules/verify-before-asking.md +7 -0
- package/rules/verify-runtime-state.md +40 -0
- package/rules/windows-filesystem-safe.md +8 -0
- package/rules/workers-done-before-complete.md +33 -0
- package/rules/workflow-substitution-slots.md +5 -0
- package/skills/CLAUDE.md +7 -2
- package/skills/autoconverge/SKILL.md +48 -61
- package/skills/autoconverge/reference/closing-report.md +6 -6
- package/skills/autoconverge/reference/convergence.md +17 -15
- package/skills/autoconverge/reference/gotchas.md +6 -3
- package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +2 -9
- package/skills/autoconverge/workflow/converge.contract.test.mjs +153 -10
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +55 -19
- package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +16 -9
- package/skills/autoconverge/workflow/converge.mjs +167 -106
- package/skills/autoconverge/workflow/render_report.py +7 -11
- package/skills/bugteam/CLAUDE.md +1 -1
- package/skills/bugteam/PROMPTS.md +7 -6
- package/skills/bugteam/SKILL.md +25 -72
- package/skills/bugteam/reference/CLAUDE.md +1 -3
- package/skills/bugteam/reference/README.md +1 -1
- package/skills/bugteam/reference/audit-and-teammates.md +1 -1
- package/skills/bugteam/reference/obstacles/fix-publish-summary.md +1 -1
- package/skills/bugteam/reference/team-setup.md +8 -7
- package/skills/bugteam/scripts/CLAUDE.md +0 -6
- package/skills/bugteam/scripts/README.md +0 -4
- package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +0 -1
- package/skills/code/SKILL.md +2 -0
- package/skills/copilot-review/CLAUDE.md +1 -1
- package/skills/copilot-review/SKILL.md +25 -23
- package/skills/everything-search/SKILL.md +5 -0
- package/skills/findbugs/CLAUDE.md +2 -2
- package/skills/findbugs/SKILL.md +22 -83
- package/skills/fixbugs/SKILL.md +2 -4
- package/skills/log-audit/CLAUDE.md +20 -0
- package/skills/log-audit/SKILL.md +68 -0
- package/skills/log-audit/reference/CLAUDE.md +9 -0
- package/skills/log-audit/reference/charter.md +52 -0
- package/skills/log-audit/scripts/CLAUDE.md +27 -0
- package/skills/log-audit/scripts/cluster_recurrences.py +261 -0
- package/skills/log-audit/scripts/collect_log_window.py +199 -0
- package/skills/log-audit/scripts/log_audit_constants/CLAUDE.md +12 -0
- package/skills/log-audit/scripts/log_audit_constants/cluster_recurrences_constants.py +23 -0
- package/skills/log-audit/scripts/log_audit_constants/collect_log_window_constants.py +24 -0
- package/skills/log-audit/scripts/log_audit_constants/mine_copilot_findings_constants.py +49 -0
- package/skills/log-audit/scripts/mine_copilot_findings.py +302 -0
- package/skills/log-audit/scripts/test_cluster_recurrences.py +160 -0
- package/skills/log-audit/scripts/test_collect_log_window.py +111 -0
- package/skills/log-audit/scripts/test_mine_copilot_findings.py +126 -0
- package/skills/monitor-open-prs/SKILL.md +2 -2
- package/skills/post-audit-findings/SKILL.md +84 -0
- package/skills/pr-converge/CLAUDE.md +2 -0
- package/skills/pr-converge/SKILL.md +72 -59
- package/skills/pr-converge/reference/CLAUDE.md +1 -1
- package/skills/pr-converge/reference/convergence-gates.md +16 -19
- package/skills/pr-converge/reference/examples.md +5 -5
- package/skills/pr-converge/reference/fix-protocol.md +16 -43
- package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +1 -1
- package/skills/pr-converge/reference/per-tick.md +24 -45
- package/skills/pr-converge/reference/state-schema.md +15 -0
- package/skills/pr-converge/scripts/README.md +3 -5
- package/skills/pr-fix-protocol/SKILL.md +70 -0
- package/skills/pr-loop-lifecycle/SKILL.md +73 -0
- package/skills/{bugteam → pr-loop-lifecycle}/reference/teardown-publish-permissions.md +22 -24
- package/skills/pr-scope-resolve/SKILL.md +48 -0
- package/skills/qbug/CLAUDE.md +4 -4
- package/skills/qbug/SKILL.md +46 -144
- package/skills/qbug/test_qbug_skill_audit_schema.py +2 -2
- package/skills/qbug/test_qbug_skill_post_fix_audit.py +1 -1
- package/skills/reviewer-gates/SKILL.md +96 -0
- package/skills/session-log/CLAUDE.md +7 -7
- package/skills/session-log/SKILL.md +27 -44
- package/skills/test_markdown_link_integrity.py +103 -0
- package/commands/doc-gist.md +0 -16
- package/hooks/blocking/_md_to_html_blocker_test_support.py +0 -65
- package/hooks/blocking/conftest.py +0 -30
- package/hooks/blocking/md_path_exemptions.py +0 -224
- package/hooks/blocking/md_to_html_blocker.py +0 -155
- package/hooks/blocking/test_md_to_html_blocker_exemptions.py +0 -434
- package/hooks/blocking/test_md_to_html_blocker_extensions.py +0 -157
- package/hooks/blocking/test_md_to_html_blocker_path_resolution.py +0 -336
- package/hooks/hooks_constants/doc_gist_auto_publish_constants.py +0 -18
- package/hooks/hooks_constants/html_companion_constants.py +0 -20
- package/hooks/hooks_constants/md_to_html_blocker_constants.py +0 -76
- package/hooks/hooks_constants/test_md_to_html_blocker_constants.py +0 -125
- package/hooks/workflow/doc_gist_auto_publish.py +0 -144
- package/hooks/workflow/md_to_html_companion.py +0 -358
- package/hooks/workflow/test_doc_gist_auto_publish.py +0 -117
- package/hooks/workflow/test_md_to_html_companion.py +0 -613
- package/skills/bugteam/reference/audit-contract.md +0 -163
- package/skills/bugteam/scripts/_bugteam_permissions_common.py +0 -455
- package/skills/bugteam/scripts/bugteam_scripts_constants/claude_permissions_common_constants.py +0 -69
- package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -280
- package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -266
- package/skills/bugteam/scripts/test__bugteam_permissions_common.py +0 -160
- package/skills/bugteam/scripts/test_agent_config_carveout.py +0 -356
- package/skills/bugteam/scripts/test_bugteam_permissions_common.py +0 -140
- package/skills/doc-gist/CLAUDE.md +0 -25
- package/skills/doc-gist/SKILL.md +0 -97
- package/skills/doc-gist/references/CLAUDE.md +0 -9
- package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +0 -453
- package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +0 -515
- package/skills/doc-gist/references/examples/03-code-review-pr.html +0 -638
- package/skills/doc-gist/references/examples/04-code-understanding.html +0 -491
- package/skills/doc-gist/references/examples/05-design-system.html +0 -629
- package/skills/doc-gist/references/examples/06-component-variants.html +0 -605
- package/skills/doc-gist/references/examples/07-prototype-animation.html +0 -455
- package/skills/doc-gist/references/examples/08-prototype-interaction.html +0 -396
- package/skills/doc-gist/references/examples/09-slide-deck.html +0 -592
- package/skills/doc-gist/references/examples/10-svg-illustrations.html +0 -492
- package/skills/doc-gist/references/examples/11-status-report.html +0 -528
- package/skills/doc-gist/references/examples/12-incident-report.html +0 -596
- package/skills/doc-gist/references/examples/13-flowchart-diagram.html +0 -395
- package/skills/doc-gist/references/examples/14-research-feature-explainer.html +0 -381
- package/skills/doc-gist/references/examples/15-research-concept-explainer.html +0 -368
- package/skills/doc-gist/references/examples/16-implementation-plan.html +0 -702
- package/skills/doc-gist/references/examples/17-pr-writeup.html +0 -595
- package/skills/doc-gist/references/examples/18-editor-triage-board.html +0 -573
- package/skills/doc-gist/references/examples/19-editor-feature-flags.html +0 -663
- package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +0 -722
- package/skills/doc-gist/references/examples/21-decision-signoff.html +0 -546
- package/skills/doc-gist/references/examples/CLAUDE.md +0 -25
- package/skills/doc-gist/references/examples/README.md +0 -5
- package/skills/doc-gist/scripts/CLAUDE.md +0 -27
- package/skills/doc-gist/scripts/doc_gist_scripts_constants/CLAUDE.md +0 -10
- package/skills/doc-gist/scripts/doc_gist_scripts_constants/gist_upload_constants.py +0 -16
- package/skills/doc-gist/scripts/gist_upload.py +0 -177
- package/skills/doc-gist/scripts/test_gist_upload.py +0 -51
- /package/skills/{doc-gist/scripts/doc_gist_scripts_constants → log-audit/scripts/log_audit_constants}/__init__.py +0 -0
package/CLAUDE.md
CHANGED
|
@@ -115,30 +115,33 @@ Before any coding task, call the `initial_instructions` tool to load the Serena
|
|
|
115
115
|
|
|
116
116
|
### Tool hierarchy for code navigation
|
|
117
117
|
1. **Serena** — symbol-level navigation (declarations, references, implementations, rename)
|
|
118
|
-
2. **
|
|
118
|
+
2. **es.exe** — file-system search by name/path/extension/size/date (Everything CLI)
|
|
119
119
|
3. **Grep/Glob** — content and pattern matching
|
|
120
120
|
|
|
121
|
-
## Everything Search (
|
|
121
|
+
## Everything Search (es.exe CLI)
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
The `everything_search` MCP tool is available in every session.
|
|
123
|
+
Search files by name, path, extension, size, or date with the Everything command-line tool `es.exe`. It reads the same live index the desktop app keeps, so results return instantly.
|
|
125
124
|
|
|
126
|
-
###
|
|
127
|
-
|
|
125
|
+
### Invocation and scope tokens
|
|
126
|
+
Run `es.exe` with a scoped query — a project path, an `ext:`/`dm:`/`size:` filter, or a name pattern. The `es_exe_path_rewriter` hook resolves a `{project-name}` placeholder or a bare registry key from `~/.claude/project-paths.json` into its quoted absolute path before the command runs (it allows and rewrites, never blocks).
|
|
127
|
+
|
|
128
|
+
### Hard limits
|
|
129
|
+
- Scope every search. A bare whole-drive scan or a network-share sweep is out of bounds.
|
|
128
130
|
|
|
129
131
|
### Fallback order
|
|
130
|
-
1. **
|
|
131
|
-
2. **
|
|
132
|
-
3. **
|
|
132
|
+
1. **es.exe** — file-system search by name/path/extension/size/date
|
|
133
|
+
2. **Debug** — try to find out why es.exe isn't working, and then prompt user for next-steps if you can't self-heal.
|
|
134
|
+
3. **Grep** — file-content search (Grep owns content)
|
|
135
|
+
4. **Glob** — relative-path pattern matching within the current project
|
|
133
136
|
|
|
134
137
|
### Search syntax quick reference
|
|
135
|
-
- `ext:py` — find by extension (multiple: `ext:ts
|
|
138
|
+
- `ext:py` — find by extension (multiple: `ext:ts | ext:js`)
|
|
136
139
|
- `path:src\components` — match against full path
|
|
137
|
-
- `count:10` — limit number of results to 10
|
|
138
140
|
- `*.config.*` — wildcards
|
|
139
141
|
- `size:>10mb` — size filter
|
|
140
142
|
- `dm:today` / `dm:thisweek` — date modified filter
|
|
141
|
-
- `
|
|
142
|
-
- `parent:node_modules package.json` — match parent folder
|
|
143
|
+
- `-n 50` — limit results; `-sort dm` — sort by date modified
|
|
143
144
|
- `foo bar` — AND, `foo | bar` — OR, `!foo` — NOT
|
|
144
145
|
- `"exact phrase"` — literal match
|
|
146
|
+
|
|
147
|
+
Full operator reference: `skills/everything-search/SKILL.md`.
|
|
@@ -8,7 +8,8 @@ Shared output schema and audit-loop contract used by `/bugteam`, `/qbug`, `/find
|
|
|
8
8
|
- Adversarial second pass
|
|
9
9
|
- Haiku secondary auditor
|
|
10
10
|
- Post-fix self-audit
|
|
11
|
-
-
|
|
11
|
+
- De-dup and merge
|
|
12
|
+
- Persistence (loop-<L>-audit.json, loop-<L>-diagnostics.json)
|
|
12
13
|
|
|
13
14
|
## Finding schema
|
|
14
15
|
|
|
@@ -18,7 +19,7 @@ Each finding an audit produces MUST be one of exactly two shapes.
|
|
|
18
19
|
|
|
19
20
|
```json
|
|
20
21
|
{
|
|
21
|
-
"id": "loop<
|
|
22
|
+
"id": "loop<L>-<K>",
|
|
22
23
|
"file": "path/relative/to/repo/root.py",
|
|
23
24
|
"line": 123,
|
|
24
25
|
"category": "A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P",
|
|
@@ -29,7 +30,7 @@ Each finding an audit produces MUST be one of exactly two shapes.
|
|
|
29
30
|
}
|
|
30
31
|
```
|
|
31
32
|
|
|
32
|
-
`id` is `loop<
|
|
33
|
+
`id` is `loop<L>-<K>` where `L` is the loop counter (1-based) and `K` is the 1-based index within the loop. For `/findbugs` which runs once, use `find<K>`.
|
|
33
34
|
|
|
34
35
|
### Shape B — structured proof-of-absence
|
|
35
36
|
|
|
@@ -98,16 +99,16 @@ For single-subagent skills (`/qbug`, `/findbugs`) the LEAD spawns two `Agent()`
|
|
|
98
99
|
|
|
99
100
|
Both audit the same diff. The secondary returns findings to the LEAD only — never posted to the PR.
|
|
100
101
|
|
|
101
|
-
Merge rules:
|
|
102
|
+
Merge rules — applied whenever the LEAD combines findings from multiple sources (primary auditor, Haiku secondary auditor, adversarial pass):
|
|
102
103
|
|
|
103
|
-
- **De-dup key**: `(file, line, category)`.
|
|
104
|
-
- **Severity conflict**: max wins (P0 > P1 > P2).
|
|
105
|
-
- **Unique-to-
|
|
104
|
+
- **De-dup key**: `(file, line, category)`. Two findings sharing the same `(file, line, category)` tuple are the same finding and collapse into one entry.
|
|
105
|
+
- **Severity conflict**: max wins (`P0 > P1 > P2`). When sources disagree on severity for the same de-dup key, the merged entry keeps the highest severity.
|
|
106
|
+
- **Unique-to-secondary findings**: added to the merged set with the secondary's severity and source annotation.
|
|
106
107
|
- **Unique-to-primary findings**: kept as-is.
|
|
107
|
-
- **Zero
|
|
108
|
-
- **Malformed or non-parseable
|
|
108
|
+
- **Zero secondary findings**: the primary set is trusted and the audit moves on.
|
|
109
|
+
- **Malformed or non-parseable secondary output**: lead trusts the primary set and logs the event in `loop-<L>-diagnostics.json` under `haiku_findings` as `[{"parse_error": "<message>"}]`.
|
|
109
110
|
|
|
110
|
-
For
|
|
111
|
+
For `/bugteam`, the single audit agent provides per-category coverage by walking all A–P rubrics in one invocation.
|
|
111
112
|
|
|
112
113
|
## Post-fix self-audit
|
|
113
114
|
|
|
@@ -128,11 +129,22 @@ Sequence:
|
|
|
128
129
|
|
|
129
130
|
`converged` exit condition: `primary_audit_clean AND post_fix_audit_clean` for the committing loop.
|
|
130
131
|
|
|
132
|
+
## De-dup and merge
|
|
133
|
+
|
|
134
|
+
Findings from primary, adversarial, Haiku secondary, and post-fix passes are merged into a single deduped finding list before persistence.
|
|
135
|
+
|
|
136
|
+
- **De-dup key:** `(file, line, category)`. Two findings sharing the same `(file, line, category)` tuple collapse into a single deduped entry.
|
|
137
|
+
- **Severity conflict resolution:** `max wins`. When merged findings disagree on severity, the deduped entry carries the highest severity (`P0 > P1 > P2`).
|
|
138
|
+
- **Excerpt and failure_mode:** the deduped entry inherits these fields from the highest-severity contributing finding. Ties keep the first observed contributor.
|
|
139
|
+
- **`evidence_files`:** the deduped entry carries the union of every contributor's `evidence_files`, deduplicated and sorted.
|
|
140
|
+
|
|
141
|
+
The merged list lands in `loop-<L>-diagnostics.json` under both `merged` (one entry per contributing finding) and `deduped` (one entry per unique `(file, line, category)` tuple).
|
|
142
|
+
|
|
131
143
|
## Persistence
|
|
132
144
|
|
|
133
145
|
Every audit loop writes two JSON files under the skill's scoped temp directory (resolved via `tempfile.gettempdir()`):
|
|
134
146
|
|
|
135
|
-
### `loop-<
|
|
147
|
+
### `loop-<L>-audit.json`
|
|
136
148
|
|
|
137
149
|
```json
|
|
138
150
|
{
|
|
@@ -142,7 +154,7 @@ Every audit loop writes two JSON files under the skill's scoped temp directory (
|
|
|
142
154
|
}
|
|
143
155
|
```
|
|
144
156
|
|
|
145
|
-
### `loop-<
|
|
157
|
+
### `loop-<L>-diagnostics.json`
|
|
146
158
|
|
|
147
159
|
```json
|
|
148
160
|
{
|
|
@@ -15,6 +15,7 @@ Python scripts invoked at runtime by the PR-loop skills. Each script is a standa
|
|
|
15
15
|
| `terminology_sweep.py` | Flags a prose term that near-misses an identifier introduced on added code lines of a unified diff (shared leading word, divergent tail) |
|
|
16
16
|
| `reviews_disabled.py` | Shared helper for the `CLAUDE_REVIEWS_DISABLED` opt-out gate; parses the env-var token to find which reviewer types are suppressed |
|
|
17
17
|
| `copilot_quota.py` | Copilot premium-request quota pre-check: resolves a configured GitHub account, reads its remaining `premium_interactions` quota via `gh api copilot_internal/user`, and exits 0 (run Copilot) or non-zero (skip: out of quota, API down, or no account configured) |
|
|
18
|
+
| `reviewer_availability.py` | Unified reviewer-availability entry point for Copilot and Bugbot: reuses `copilot_quota.py` and `reviews_disabled.py` and exits 0 when the named `--reviewer` may be spawned, non-zero when it is opted out or (for Copilot) out of quota |
|
|
18
19
|
| `fix_hookspath.py` | Repairs a malformed `core.hooksPath` global git config entry |
|
|
19
20
|
| `_claude_permissions_common.py` | Internal helpers shared by the grant/revoke scripts: atomic settings.json writes, list mutation, path helpers |
|
|
20
21
|
|
|
@@ -7,6 +7,7 @@ Runnable helpers used by **bugteam**, **qbug**, **pr-converge**, and related ski
|
|
|
7
7
|
| File | Purpose |
|
|
8
8
|
| --- | --- |
|
|
9
9
|
| `preflight.py` | Local checks before a PR-loop run (pytest discovery, optional pre-commit, hooksPath sanity). |
|
|
10
|
+
| `reviewer_availability.py` | Unified availability check for Copilot and Bugbot; reuses `copilot_quota.py` and `reviews_disabled.py` and exits 0 when the named `--reviewer` may be spawned. |
|
|
10
11
|
| `code_rules_gate.py` | CODE_RULES gate over PR-scoped diffs (`--base`, staged-only, path filters). |
|
|
11
12
|
| `fix_hookspath.py` | Repair `core.hooksPath` when it does not point at the packaged git-hooks tree. |
|
|
12
13
|
| `grant_project_claude_permissions.py` / `revoke_project_claude_permissions.py` | Claude Code permission JSON helpers used during publish-style flows. |
|
|
@@ -299,9 +299,11 @@ def write_atomically_with_mode(
|
|
|
299
299
|
|
|
300
300
|
Raises:
|
|
301
301
|
OSError: When os.open or os.fdopen fails. The raw file descriptor
|
|
302
|
-
is closed
|
|
302
|
+
is closed and the on-disk temporary file is unlinked before
|
|
303
|
+
re-raising so neither the descriptor nor the file leaks.
|
|
303
304
|
MemoryError: When os.fdopen runs out of buffer memory; the file
|
|
304
|
-
descriptor is closed
|
|
305
|
+
descriptor is closed and the temporary file is unlinked
|
|
306
|
+
before re-raising.
|
|
305
307
|
"""
|
|
306
308
|
file_descriptor = os.open(
|
|
307
309
|
str(temporary_path),
|
|
@@ -312,6 +314,10 @@ def write_atomically_with_mode(
|
|
|
312
314
|
writer = os.fdopen(file_descriptor, "w", encoding=TEXT_FILE_ENCODING)
|
|
313
315
|
except (OSError, MemoryError):
|
|
314
316
|
os.close(file_descriptor)
|
|
317
|
+
try:
|
|
318
|
+
os.unlink(str(temporary_path))
|
|
319
|
+
except OSError:
|
|
320
|
+
pass
|
|
315
321
|
raise
|
|
316
322
|
with writer:
|
|
317
323
|
writer.write(serialized_content)
|
|
@@ -321,7 +327,8 @@ def save_settings(settings_path: Path, all_settings: dict[str, object]) -> None:
|
|
|
321
327
|
"""Write settings to a JSON file atomically with permission preservation.
|
|
322
328
|
|
|
323
329
|
Creates a temporary sibling file, writes content, then atomically
|
|
324
|
-
replaces the target. Cleans up the temporary file in a finally block
|
|
330
|
+
replaces the target. Cleans up the temporary file in a finally block,
|
|
331
|
+
printing a warning to stderr when that cleanup unlink fails.
|
|
325
332
|
|
|
326
333
|
Args:
|
|
327
334
|
settings_path: Path to the target settings JSON file.
|
|
@@ -352,8 +359,12 @@ def save_settings(settings_path: Path, all_settings: dict[str, object]) -> None:
|
|
|
352
359
|
if temporary_path.exists():
|
|
353
360
|
try:
|
|
354
361
|
temporary_path.unlink()
|
|
355
|
-
except OSError:
|
|
356
|
-
|
|
362
|
+
except OSError as unlink_error:
|
|
363
|
+
print(
|
|
364
|
+
f"Warning: could not remove temp file {temporary_path}: "
|
|
365
|
+
f"{type(unlink_error).__name__}: {unlink_error}",
|
|
366
|
+
file=sys.stderr,
|
|
367
|
+
)
|
|
357
368
|
|
|
358
369
|
|
|
359
370
|
def append_if_missing(all_target_list: list[object], new_value: str) -> bool:
|
|
@@ -1673,7 +1673,6 @@ def main(all_arguments: list[str]) -> int:
|
|
|
1673
1673
|
if arguments.staged:
|
|
1674
1674
|
all_terminology_findings = staged_terminology_findings(repository_root)
|
|
1675
1675
|
_report_terminology_findings(all_terminology_findings)
|
|
1676
|
-
terminology_exit_code = 1 if all_terminology_findings else 0
|
|
1677
1676
|
staged_test_exit_code = run_staged_test_files(repository_root)
|
|
1678
1677
|
staged_file_paths = paths_from_git_staged(repository_root)
|
|
1679
1678
|
staged_file_paths = filter_paths_under_prefixes(
|
|
@@ -1682,7 +1681,7 @@ def main(all_arguments: list[str]) -> int:
|
|
|
1682
1681
|
arguments.only_under,
|
|
1683
1682
|
)
|
|
1684
1683
|
if not staged_file_paths:
|
|
1685
|
-
return
|
|
1684
|
+
return staged_test_exit_code
|
|
1686
1685
|
staged_added_lines = added_lines_by_file_staged(
|
|
1687
1686
|
repository_root, staged_file_paths
|
|
1688
1687
|
)
|
|
@@ -1693,7 +1692,7 @@ def main(all_arguments: list[str]) -> int:
|
|
|
1693
1692
|
all_added_lines_by_path=staged_added_lines,
|
|
1694
1693
|
read_staged_content_flag=True,
|
|
1695
1694
|
)
|
|
1696
|
-
return gate_exit_code or
|
|
1695
|
+
return gate_exit_code or staged_test_exit_code
|
|
1697
1696
|
file_paths = paths_from_git_diff(repository_root, arguments.base)
|
|
1698
1697
|
file_paths = filter_paths_under_prefixes(
|
|
1699
1698
|
file_paths,
|
|
@@ -17,6 +17,7 @@ Named constants for every script in `_shared/pr-loop/scripts/`. Each module owns
|
|
|
17
17
|
| `reviews_disabled_constants.py` | `reviews_disabled.py` — `CLAUDE_REVIEWS_DISABLED` token taxonomy |
|
|
18
18
|
| `copilot_quota_constants.py` | `copilot_quota.py` — `COPILOT_QUOTA_ACCOUNT` env-var name, `copilot_internal/user` API path, the `premium_interactions` gating field names, the four skip/run exit codes, and the default `.env` path |
|
|
19
19
|
| `terminology_sweep_constants.py` | `terminology_sweep.py` — identifier and prose-token regexes, diff-parsing prefixes, code-file extensions, and the finding-message template |
|
|
20
|
+
| `reviewer_availability_constants.py` | `reviewer_availability.py` — the available/down exit codes for the unified reviewer-availability pre-check |
|
|
20
21
|
| `__init__.py` | Empty package marker |
|
|
21
22
|
|
|
22
23
|
## Convention
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Configuration constants for the unified reviewer-availability pre-check.
|
|
2
|
+
|
|
3
|
+
The down code is 3 so a deliberate reviewer-down report stays distinguishable
|
|
4
|
+
from a generic interpreter crash (exit 1) and an argparse usage error
|
|
5
|
+
(exit 2). A caller that gates on the down code treats any other non-zero exit
|
|
6
|
+
as a broken check, not a down reviewer, and fails open.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
EXIT_CODE_REVIEWER_AVAILABLE: int = 0
|
|
12
|
+
EXIT_CODE_REVIEWER_DOWN: int = 3
|
|
@@ -24,8 +24,6 @@ HYPHENATED_PROSE_TOKEN_PATTERN: re.Pattern[str] = re.compile(
|
|
|
24
24
|
r"\b[A-Za-z][A-Za-z0-9]*(?:-[A-Za-z][A-Za-z0-9]*)+\b"
|
|
25
25
|
)
|
|
26
26
|
|
|
27
|
-
PROSE_WORD_PATTERN: re.Pattern[str] = re.compile(r"[A-Za-z][A-Za-z0-9]*")
|
|
28
|
-
|
|
29
27
|
STRING_LITERAL_CONTENT_PATTERN: re.Pattern[str] = re.compile(
|
|
30
28
|
r"\"([^\"]*)\"|'([^']*)'|`([^`]*)`"
|
|
31
29
|
)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Unified reviewer-availability pre-check for GitHub Copilot and Cursor Bugbot.
|
|
2
|
+
|
|
3
|
+
Run this before either reviewer step spawns. It answers one question: is the
|
|
4
|
+
named reviewer available to run right now. It reuses two existing checks
|
|
5
|
+
instead of re-implementing their rules. The ``CLAUDE_REVIEWS_DISABLED``
|
|
6
|
+
opt-out gate lives in ``reviews_disabled.py``. The Copilot premium-interaction
|
|
7
|
+
quota pre-check lives in ``copilot_quota.py``.
|
|
8
|
+
|
|
9
|
+
Copilot counts as down in two cases: it is opted out via
|
|
10
|
+
``CLAUDE_REVIEWS_DISABLED``, or its quota pre-check returns anything other
|
|
11
|
+
than quota available (out of quota, the quota API or account down, or no
|
|
12
|
+
account configured).
|
|
13
|
+
|
|
14
|
+
Bugbot carries no quota or availability API. It counts as down only when it
|
|
15
|
+
is opted out via ``CLAUDE_REVIEWS_DISABLED``. A genuine runtime outage shows
|
|
16
|
+
up later as a poll timeout, not here.
|
|
17
|
+
|
|
18
|
+
The exit code tells the caller what to do. Exit 0 means the reviewer is
|
|
19
|
+
available and may be spawned. The documented down code (3) means skip it. Any
|
|
20
|
+
other non-zero exit is a broken check — an interpreter crash or a usage
|
|
21
|
+
error — not a down report, so a gating caller fails open on it. Every path
|
|
22
|
+
prints one line: available lines go to stdout, down lines go to stderr.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import argparse
|
|
28
|
+
import sys
|
|
29
|
+
from dataclasses import dataclass
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
from copilot_quota import (
|
|
33
|
+
COPILOT_QUOTA_DEFAULT_ENV_FILE_PATH,
|
|
34
|
+
EXIT_CODE_QUOTA_AVAILABLE,
|
|
35
|
+
evaluate_copilot_quota,
|
|
36
|
+
)
|
|
37
|
+
from pr_loop_shared_constants.reviewer_availability_constants import (
|
|
38
|
+
EXIT_CODE_REVIEWER_AVAILABLE,
|
|
39
|
+
EXIT_CODE_REVIEWER_DOWN,
|
|
40
|
+
)
|
|
41
|
+
from pr_loop_shared_constants.reviews_disabled_constants import (
|
|
42
|
+
CLAUDE_REVIEWS_DISABLED_BUGBOT_TOKEN,
|
|
43
|
+
CLAUDE_REVIEWS_DISABLED_COPILOT_TOKEN,
|
|
44
|
+
CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME,
|
|
45
|
+
)
|
|
46
|
+
from reviews_disabled import is_bugbot_disabled_via_env, is_copilot_disabled_via_env
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class ReviewerAvailability:
|
|
51
|
+
"""One reviewer-availability outcome: the exit code and the single log line.
|
|
52
|
+
|
|
53
|
+
The exit code tells the caller what to do, where 0 means the reviewer may
|
|
54
|
+
be spawned and the documented down code (3) means skip it. The CLI prints
|
|
55
|
+
the message on one line, to stdout when available and to stderr when down.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
exit_code: int
|
|
59
|
+
message: str
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _evaluate_copilot_availability(env_file_path: Path) -> ReviewerAvailability:
|
|
63
|
+
"""Decide whether GitHub Copilot is available to spawn.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
env_file_path: The ``.env`` file the quota pre-check consults for the
|
|
67
|
+
configured account when neither the flag nor the environment
|
|
68
|
+
variable names one.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
A ReviewerAvailability that is down when Copilot is opted out via
|
|
72
|
+
``CLAUDE_REVIEWS_DISABLED`` or the quota pre-check reports anything
|
|
73
|
+
other than quota available, and available otherwise.
|
|
74
|
+
"""
|
|
75
|
+
if is_copilot_disabled_via_env():
|
|
76
|
+
return ReviewerAvailability(
|
|
77
|
+
EXIT_CODE_REVIEWER_DOWN,
|
|
78
|
+
f"reviewer-availability: copilot is disabled via "
|
|
79
|
+
f"{CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME} — skipping.",
|
|
80
|
+
)
|
|
81
|
+
quota_decision = evaluate_copilot_quota(cli_account=None, env_file_path=env_file_path)
|
|
82
|
+
if quota_decision.exit_code != EXIT_CODE_QUOTA_AVAILABLE:
|
|
83
|
+
return ReviewerAvailability(EXIT_CODE_REVIEWER_DOWN, quota_decision.message)
|
|
84
|
+
return ReviewerAvailability(EXIT_CODE_REVIEWER_AVAILABLE, quota_decision.message)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _evaluate_bugbot_availability() -> ReviewerAvailability:
|
|
88
|
+
"""Decide whether Cursor Bugbot is available to spawn.
|
|
89
|
+
|
|
90
|
+
Bugbot carries no quota or availability API, so this checks only the
|
|
91
|
+
deterministic ``CLAUDE_REVIEWS_DISABLED`` opt-out.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
A ReviewerAvailability that is down when Bugbot is opted out via
|
|
95
|
+
``CLAUDE_REVIEWS_DISABLED``, and available otherwise.
|
|
96
|
+
"""
|
|
97
|
+
if is_bugbot_disabled_via_env():
|
|
98
|
+
return ReviewerAvailability(
|
|
99
|
+
EXIT_CODE_REVIEWER_DOWN,
|
|
100
|
+
f"reviewer-availability: bugbot is disabled via "
|
|
101
|
+
f"{CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME} — skipping.",
|
|
102
|
+
)
|
|
103
|
+
return ReviewerAvailability(
|
|
104
|
+
EXIT_CODE_REVIEWER_AVAILABLE,
|
|
105
|
+
"reviewer-availability: bugbot is available.",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def evaluate_reviewer_availability(
|
|
110
|
+
reviewer_token: str, env_file_path: Path
|
|
111
|
+
) -> ReviewerAvailability:
|
|
112
|
+
"""Decide whether the named reviewer is available to spawn.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
reviewer_token: Either the Copilot or the Bugbot reviewer token.
|
|
116
|
+
env_file_path: The ``.env`` file the Copilot quota pre-check consults
|
|
117
|
+
for the configured account.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
The ReviewerAvailability for the named reviewer.
|
|
121
|
+
|
|
122
|
+
Raises:
|
|
123
|
+
ValueError: When reviewer_token names neither the Copilot nor the
|
|
124
|
+
Bugbot reviewer token.
|
|
125
|
+
"""
|
|
126
|
+
if reviewer_token == CLAUDE_REVIEWS_DISABLED_COPILOT_TOKEN:
|
|
127
|
+
return _evaluate_copilot_availability(env_file_path)
|
|
128
|
+
if reviewer_token == CLAUDE_REVIEWS_DISABLED_BUGBOT_TOKEN:
|
|
129
|
+
return _evaluate_bugbot_availability()
|
|
130
|
+
raise ValueError(f"unknown reviewer token: {reviewer_token}")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _parse_arguments(all_argv: list[str]) -> argparse.Namespace:
|
|
134
|
+
"""Parse command-line arguments for the reviewer-availability check.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
all_argv: Argument list excluding the program name, typically
|
|
138
|
+
``sys.argv[1:]``.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Namespace exposing a ``reviewer`` attribute constrained to the
|
|
142
|
+
Copilot and Bugbot reviewer tokens.
|
|
143
|
+
"""
|
|
144
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
145
|
+
parser.add_argument(
|
|
146
|
+
"--reviewer",
|
|
147
|
+
required=True,
|
|
148
|
+
choices=[
|
|
149
|
+
CLAUDE_REVIEWS_DISABLED_COPILOT_TOKEN,
|
|
150
|
+
CLAUDE_REVIEWS_DISABLED_BUGBOT_TOKEN,
|
|
151
|
+
],
|
|
152
|
+
help="Reviewer to check availability for",
|
|
153
|
+
)
|
|
154
|
+
return parser.parse_args(all_argv)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def main(all_arguments: list[str]) -> int:
|
|
158
|
+
"""Run the reviewer-availability check end-to-end and print its decision.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
all_arguments: Argument list excluding the program name.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
0 when the named reviewer is available to spawn, the documented
|
|
165
|
+
reviewer-down exit code when it is down.
|
|
166
|
+
"""
|
|
167
|
+
arguments = _parse_arguments(all_arguments)
|
|
168
|
+
availability = evaluate_reviewer_availability(
|
|
169
|
+
reviewer_token=arguments.reviewer,
|
|
170
|
+
env_file_path=COPILOT_QUOTA_DEFAULT_ENV_FILE_PATH,
|
|
171
|
+
)
|
|
172
|
+
message_stream = (
|
|
173
|
+
sys.stdout
|
|
174
|
+
if availability.exit_code == EXIT_CODE_REVIEWER_AVAILABLE
|
|
175
|
+
else sys.stderr
|
|
176
|
+
)
|
|
177
|
+
print(availability.message, file=message_stream)
|
|
178
|
+
return availability.exit_code
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
if __name__ == "__main__":
|
|
182
|
+
raise SystemExit(main(sys.argv[1:]))
|
|
@@ -103,6 +103,7 @@ def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
|
|
|
103
103
|
choices=[
|
|
104
104
|
CLAUDE_REVIEWS_DISABLED_BUGBOT_TOKEN,
|
|
105
105
|
CLAUDE_REVIEWS_DISABLED_BUGTEAM_TOKEN,
|
|
106
|
+
CLAUDE_REVIEWS_DISABLED_COPILOT_TOKEN,
|
|
106
107
|
],
|
|
107
108
|
help="Reviewer token to test against CLAUDE_REVIEWS_DISABLED",
|
|
108
109
|
)
|
|
@@ -122,6 +123,7 @@ def main(all_arguments: list[str]) -> int:
|
|
|
122
123
|
disabled_checker_by_reviewer = {
|
|
123
124
|
CLAUDE_REVIEWS_DISABLED_BUGBOT_TOKEN: is_bugbot_disabled_via_env,
|
|
124
125
|
CLAUDE_REVIEWS_DISABLED_BUGTEAM_TOKEN: is_bugteam_disabled_via_env,
|
|
126
|
+
CLAUDE_REVIEWS_DISABLED_COPILOT_TOKEN: is_copilot_disabled_via_env,
|
|
125
127
|
}
|
|
126
128
|
return 0 if disabled_checker_by_reviewer[arguments.reviewer]() else 1
|
|
127
129
|
|
|
@@ -7,11 +7,18 @@ finds the other.
|
|
|
7
7
|
|
|
8
8
|
This sweep reads a unified diff and collects every multi-word identifier added
|
|
9
9
|
on code lines. Then it scans each added prose line — a Markdown line, or a
|
|
10
|
-
comment, docstring, or string inside a code file. It flags a
|
|
11
|
-
|
|
10
|
+
comment, docstring, or string inside a code file. It flags a hyphenated term
|
|
11
|
+
that shares an identifier's leading word but diverges in the tail. Each
|
|
12
12
|
near-miss prints as one ``file:line`` finding, and the run exits non-zero when
|
|
13
13
|
any finding remains, so a commit gate can block on it.
|
|
14
14
|
|
|
15
|
+
Only hyphenated terms are candidates. A hyphen marks a deliberate compound —
|
|
16
|
+
the author bound the words into one term — so a divergent tail there is a real
|
|
17
|
+
naming drift. A bare spaced word run is ordinary prose: windowing every
|
|
18
|
+
sentence that starts with an identifier's first word ("each attempt", "file
|
|
19
|
+
exists") floods a vocabulary-dense change with false findings and gives the
|
|
20
|
+
gate nothing actionable.
|
|
21
|
+
|
|
15
22
|
A shared leading token alone is too weak a signal: ordinary English compounds
|
|
16
23
|
such as ``read-only`` and ``data-driven`` collide with unrelated identifiers
|
|
17
24
|
(``read_config``, ``data_source``) and would block a commit falsely. To keep the
|
|
@@ -56,7 +63,6 @@ from pr_loop_shared_constants.terminology_sweep_constants import ( # noqa: E402
|
|
|
56
63
|
JSDOC_CONTINUATION_MARKER,
|
|
57
64
|
MARKDOWN_FILE_EXTENSION,
|
|
58
65
|
MINIMUM_IDENTIFIER_TOKEN_COUNT,
|
|
59
|
-
PROSE_WORD_PATTERN,
|
|
60
66
|
PYTHON_COMMENT_MARKER,
|
|
61
67
|
SNAKE_CASE_IDENTIFIER_PATTERN,
|
|
62
68
|
STRING_LITERAL_CONTENT_PATTERN,
|
|
@@ -234,35 +240,6 @@ def _hyphenated_candidates(fragment: str) -> list[tuple[str, IdentifierTuple]]:
|
|
|
234
240
|
return all_candidates
|
|
235
241
|
|
|
236
242
|
|
|
237
|
-
def _spaced_candidates(
|
|
238
|
-
fragment: str,
|
|
239
|
-
identifiers_by_first_token: dict[str, list[IdentifierTuple]],
|
|
240
|
-
) -> list[tuple[str, IdentifierTuple]]:
|
|
241
|
-
"""Return spaced word windows whose first word matches an identifier prefix.
|
|
242
|
-
|
|
243
|
-
Only a window anchored at a known identifier prefix and sized to one of that
|
|
244
|
-
prefix's identifiers is returned, which bounds the windows to the shapes the
|
|
245
|
-
near-miss check can act on.
|
|
246
|
-
|
|
247
|
-
Args:
|
|
248
|
-
fragment: The prose fragment to scan.
|
|
249
|
-
identifiers_by_first_token: Identifier tuples grouped by leading token.
|
|
250
|
-
|
|
251
|
-
Returns:
|
|
252
|
-
``(display, token_tuple)`` pairs for each candidate window.
|
|
253
|
-
"""
|
|
254
|
-
all_words = [
|
|
255
|
-
each_word.lower() for each_word in PROSE_WORD_PATTERN.findall(fragment)
|
|
256
|
-
]
|
|
257
|
-
all_candidates: list[tuple[str, IdentifierTuple]] = []
|
|
258
|
-
for each_index, each_word in enumerate(all_words):
|
|
259
|
-
for each_identifier in identifiers_by_first_token.get(each_word, []):
|
|
260
|
-
window = tuple(all_words[each_index : each_index + len(each_identifier)])
|
|
261
|
-
if len(window) == len(each_identifier):
|
|
262
|
-
all_candidates.append((" ".join(window), window))
|
|
263
|
-
return all_candidates
|
|
264
|
-
|
|
265
|
-
|
|
266
243
|
def _tokens_are_plural_variants(first_word: str, second_word: str) -> bool:
|
|
267
244
|
"""Return whether two words are singular/plural forms of one word.
|
|
268
245
|
|
|
@@ -348,7 +325,6 @@ def _findings_for_line(
|
|
|
348
325
|
reported_tuples: set[IdentifierTuple] = set()
|
|
349
326
|
for each_fragment in _prose_fragments(file_path, line_text):
|
|
350
327
|
all_candidates = _hyphenated_candidates(each_fragment)
|
|
351
|
-
all_candidates += _spaced_candidates(each_fragment, identifiers_by_first_token)
|
|
352
328
|
for each_display, each_tuple in all_candidates:
|
|
353
329
|
matched_identifier = _near_miss_identifier(
|
|
354
330
|
each_tuple, all_identifier_tuples, identifiers_by_first_token
|
|
@@ -24,6 +24,8 @@ pytest suite for the scripts and constants in `_shared/pr-loop/scripts/`. Each t
|
|
|
24
24
|
| `test_reviews_disabled.py` | `reviews_disabled.py` opt-out gate parsing |
|
|
25
25
|
| `test_copilot_quota.py` | `copilot_quota.py` end-to-end: account resolution, premium-quota classification, exit codes, and skip logging |
|
|
26
26
|
| `test_copilot_quota_constants.py` | `pr_loop_shared_constants/copilot_quota_constants.py` |
|
|
27
|
+
| `test_reviewer_availability.py` | `reviewer_availability.py` end-to-end: Copilot and Bugbot availability, opt-out via `CLAUDE_REVIEWS_DISABLED`, and every Copilot quota outcome |
|
|
28
|
+
| `test_reviewer_availability_constants.py` | `pr_loop_shared_constants/reviewer_availability_constants.py` |
|
|
27
29
|
| `test_revoke_project_claude_permissions.py` | `revoke_project_claude_permissions.py` end-to-end |
|
|
28
30
|
| `test_agent_config_carveout.py` | Agent-config deny-rule carve-out logic |
|
|
29
31
|
| `conftest.py` | Shared pytest fixtures |
|