claude-dev-env 1.80.0 → 1.81.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/_shared/pr-loop/scripts/CLAUDE.md +2 -1
- package/_shared/pr-loop/scripts/code_rules_gate.py +116 -30
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +13 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +113 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +467 -0
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +339 -0
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +297 -0
- package/audit-rubrics/CLAUDE.md +3 -2
- package/audit-rubrics/category_rubrics/CLAUDE.md +2 -1
- package/audit-rubrics/category_rubrics/category-a-api-contracts.md +2 -1
- package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +13 -1
- package/audit-rubrics/category_rubrics/category-p-name-vs-behavior-contract.md +19 -0
- package/audit-rubrics/category_rubrics/category-q-cross-surface-claims.md +46 -0
- package/audit-rubrics/prompts/CLAUDE.md +2 -1
- package/audit-rubrics/prompts/category-a-api-contracts.md +1 -0
- package/audit-rubrics/prompts/category-j-code-rules-compliance.md +19 -7
- package/audit-rubrics/prompts/category-p-name-vs-behavior-contract.md +14 -0
- package/audit-rubrics/prompts/category-q-cross-surface-claims.md +51 -0
- package/docs/CODE_RULES.md +2 -2
- package/hooks/blocking/CLAUDE.md +2 -0
- package/hooks/blocking/code_rules_annotations_length.py +59 -11
- package/hooks/blocking/code_rules_banned_identifiers.py +48 -9
- package/hooks/blocking/code_rules_command_dispatch.py +140 -0
- package/hooks/blocking/code_rules_docstrings.py +93 -0
- package/hooks/blocking/code_rules_enforcer.py +54 -4
- package/hooks/blocking/code_rules_js_conventions.py +246 -0
- package/hooks/blocking/code_rules_naming_collection.py +5 -0
- package/hooks/blocking/code_rules_test_assertions.py +3 -0
- package/hooks/blocking/code_rules_unused_imports.py +0 -3
- package/hooks/blocking/duplicate_rmtree_helper_blocker.py +7 -0
- package/hooks/blocking/test_code_rules_command_dispatch.py +95 -0
- package/hooks/blocking/test_code_rules_enforcer_annotations.py +20 -2
- package/hooks/blocking/test_code_rules_enforcer_banned_identifier.py +10 -2
- package/hooks/blocking/test_code_rules_enforcer_banned_import_alias.py +8 -4
- package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +9 -3
- package/hooks/blocking/test_code_rules_enforcer_docstring_type_checking_gate.py +164 -0
- package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +16 -3
- package/hooks/blocking/test_code_rules_js_conventions.py +167 -0
- package/hooks/hooks_constants/CLAUDE.md +2 -0
- package/hooks/hooks_constants/blocking_check_limits.py +9 -0
- package/hooks/hooks_constants/command_dispatch_constants.py +28 -0
- package/hooks/hooks_constants/js_conventions_constants.py +54 -0
- package/package.json +1 -1
- package/rules/docstring-prose-matches-implementation.md +2 -1
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +43 -1
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +2 -2
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +1 -4
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +100 -4
- package/skills/autoconverge/SKILL.md +2 -2
- package/skills/autoconverge/reference/convergence.md +3 -3
- package/skills/autoconverge/reference/stop-conditions.md +1 -1
- package/skills/autoconverge/workflow/converge.contract.test.mjs +2 -2
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +245 -1
- package/skills/autoconverge/workflow/converge.mjs +129 -23
|
@@ -11,7 +11,8 @@ Python scripts invoked at runtime by the PR-loop skills. Each script is a standa
|
|
|
11
11
|
| `post_audit_thread.py` | Posts an audit review (APPROVE / REQUEST_CHANGES) to a draft PR via the GitHub reviews API; reads the body skeleton from `audit-reply-template.md` at runtime |
|
|
12
12
|
| `grant_project_claude_permissions.py` | Writes idempotent allow-rules and `additionalDirectories` entries into `~/.claude/settings.json` so subagents can edit the project's `.claude/` tree without prompting |
|
|
13
13
|
| `revoke_project_claude_permissions.py` | Removes the allow-rules and entries that `grant_project_claude_permissions.py` wrote; safe to run when no prior grant exists |
|
|
14
|
-
| `code_rules_gate.py` | Pre-commit gate that runs `code_rules_enforcer` checks on staged Python files before a fix commit lands |
|
|
14
|
+
| `code_rules_gate.py` | Pre-commit gate that runs `code_rules_enforcer` checks on staged Python files before a fix commit lands, and the terminology sweep over the staged diff |
|
|
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) |
|
|
15
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 |
|
|
16
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) |
|
|
17
18
|
| `fix_hookspath.py` | Repairs a malformed `core.hooksPath` global git config entry |
|
|
@@ -15,16 +15,17 @@ from pr_loop_shared_constants.code_rules_gate_constants import ( # noqa: E402
|
|
|
15
15
|
ALL_CODE_FILE_EXTENSIONS,
|
|
16
16
|
ALL_GIT_DIFF_CACHED_NAME_ONLY_NULL_TERMINATED_COMMAND,
|
|
17
17
|
ALL_GIT_DIFF_NAME_ONLY_NULL_TERMINATED_COMMAND_PREFIX,
|
|
18
|
+
ALL_PYTEST_MODULE_INVOCATION,
|
|
18
19
|
ALL_TEST_FILENAME_GLOB_SUFFIXES,
|
|
19
20
|
ALL_TEST_FILENAME_SUFFIXES,
|
|
20
|
-
EXPECTED_NON_RENAME_COLUMN_COUNT,
|
|
21
|
-
EXPECTED_RENAME_COLUMN_COUNT,
|
|
22
21
|
BANNED_NOUN_DEFINITION_LINE_GROUP_INDEX,
|
|
23
22
|
BANNED_NOUN_SPAN_GROUP_INDEX,
|
|
24
23
|
BANNED_NOUN_VIOLATION_PATTERN,
|
|
25
24
|
DUPLICATE_BODY_DEFINITION_LINE_GROUP_INDEX,
|
|
26
25
|
DUPLICATE_BODY_SPAN_GROUP_INDEX,
|
|
27
26
|
DUPLICATE_BODY_VIOLATION_PATTERN,
|
|
27
|
+
EXPECTED_NON_RENAME_COLUMN_COUNT,
|
|
28
|
+
EXPECTED_RENAME_COLUMN_COUNT,
|
|
28
29
|
FUNCTION_LENGTH_DEFINITION_LINE_GROUP_INDEX,
|
|
29
30
|
FUNCTION_LENGTH_SPAN_GROUP_INDEX,
|
|
30
31
|
FUNCTION_LENGTH_VIOLATION_PATTERN,
|
|
@@ -35,10 +36,15 @@ from pr_loop_shared_constants.code_rules_gate_constants import ( # noqa: E402
|
|
|
35
36
|
ISOLATION_VIOLATION_PATTERN,
|
|
36
37
|
MAX_VIOLATIONS_PER_CHECK,
|
|
37
38
|
PYTHON_FILE_EXTENSION,
|
|
39
|
+
STAGED_PYTEST_TIMEOUT_SECONDS,
|
|
40
|
+
STAGED_TEST_FAILURE_HEADER,
|
|
38
41
|
TEST_CONFTEST_FILENAME,
|
|
39
42
|
TEST_FILENAME_PREFIX,
|
|
40
43
|
TESTS_PATH_SEGMENT,
|
|
41
44
|
)
|
|
45
|
+
from pr_loop_shared_constants.preflight_constants import ( # noqa: E402
|
|
46
|
+
PYTEST_NO_TESTS_COLLECTED_EXIT_CODE,
|
|
47
|
+
)
|
|
42
48
|
from pr_loop_shared_constants.inline_duplicate_body_span_constants import ( # noqa: E402
|
|
43
49
|
INLINE_DUPLICATE_BODY_ENCLOSING_LINE_GROUP_INDEX,
|
|
44
50
|
INLINE_DUPLICATE_BODY_ENCLOSING_SPAN_GROUP_INDEX,
|
|
@@ -46,7 +52,10 @@ from pr_loop_shared_constants.inline_duplicate_body_span_constants import ( # n
|
|
|
46
52
|
INLINE_DUPLICATE_BODY_HELPER_SPAN_GROUP_INDEX,
|
|
47
53
|
INLINE_DUPLICATE_BODY_VIOLATION_PATTERN,
|
|
48
54
|
)
|
|
49
|
-
|
|
55
|
+
from pr_loop_shared_constants.terminology_sweep_constants import ( # noqa: E402
|
|
56
|
+
TERMINOLOGY_SWEEP_GATE_HEADER,
|
|
57
|
+
)
|
|
58
|
+
from terminology_sweep import staged_terminology_findings # noqa: E402
|
|
50
59
|
|
|
51
60
|
ValidateContentCallable = Callable[..., list[str]]
|
|
52
61
|
|
|
@@ -768,12 +777,13 @@ def renamed_file_source_map_since(
|
|
|
768
777
|
|
|
769
778
|
Runs `git diff --name-status -M -z merge_base..HEAD` and collects both
|
|
770
779
|
paths of every rename entry (status code starting with R, e.g. `R100`).
|
|
771
|
-
Keys are destination posix paths; values are source posix paths.
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
780
|
+
Keys are destination posix paths; values are source posix paths.
|
|
781
|
+
|
|
782
|
+
The -z flag asks git for null-terminated, unquoted output. A path
|
|
783
|
+
holding a tab or newline byte then survives column and line splitting
|
|
784
|
+
unmangled. Each rename record emits three null-terminated tokens
|
|
785
|
+
(status, source, destination). Every other status record emits two
|
|
786
|
+
(status, path).
|
|
777
787
|
|
|
778
788
|
Args:
|
|
779
789
|
repository_root: Repository root used as the ``git -C`` target.
|
|
@@ -1024,11 +1034,13 @@ def duplicate_body_span_range(violation_text: str) -> range | None:
|
|
|
1024
1034
|
The duplicate-body message carries the copied function's definition line and
|
|
1025
1035
|
its full body span: ``Function 'NAME' duplicates location.py::name — ...
|
|
1026
1036
|
(duplicate body span at line X, spanning Y lines)``. The function occupies
|
|
1027
|
-
lines ``X`` through ``X + Y - 1`` inclusive
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1037
|
+
lines ``X`` through ``X + Y - 1`` inclusive.
|
|
1038
|
+
|
|
1039
|
+
So a duplicate of a sibling helper blocks only when the diff touches the
|
|
1040
|
+
copied function. An unrelated edit that leaves a pre-existing copy
|
|
1041
|
+
untouched keeps it advisory. This matches the span-scoped PreToolUse
|
|
1042
|
+
Write/Edit behavior rather than blocking every duplicate-body message
|
|
1043
|
+
unconditionally.
|
|
1032
1044
|
|
|
1033
1045
|
Args:
|
|
1034
1046
|
violation_text: A single violation string emitted by the enforcer.
|
|
@@ -1048,16 +1060,18 @@ def duplicate_body_span_range(violation_text: str) -> range | None:
|
|
|
1048
1060
|
def inline_duplicate_body_span_lines(violation_text: str) -> frozenset[int] | None:
|
|
1049
1061
|
"""Return the union of both spans of a same-file inline-duplicate issue, or None.
|
|
1050
1062
|
|
|
1051
|
-
The same-file inline-duplicate message names two functions that share a body
|
|
1052
|
-
the helper and the enclosing function carrying the inline copy
|
|
1053
|
-
Write/Edit hook scopes the violation by the
|
|
1054
|
-
an edit touches either function. So the message carries both spans:
|
|
1055
|
-
duplicate body spans: helper at line H spanning P lines,
|
|
1056
|
-
spanning Q lines)``.
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1063
|
+
The same-file inline-duplicate message names two functions that share a body:
|
|
1064
|
+
the helper and the enclosing function carrying the inline copy. The live
|
|
1065
|
+
Write/Edit hook scopes the violation by the union of both spans. It blocks
|
|
1066
|
+
when an edit touches either function. So the message carries both spans:
|
|
1067
|
+
``(inline duplicate body spans: helper at line H spanning P lines,
|
|
1068
|
+
enclosing at line E spanning Q lines)``.
|
|
1069
|
+
|
|
1070
|
+
The two spans can be disjoint: an unrelated function may sit between the
|
|
1071
|
+
helper and its inline copy. This returns the union as a line-number set
|
|
1072
|
+
rather than a single contiguous range. A range covering the gap would
|
|
1073
|
+
wrongly block an edit confined to that intervening function, which the
|
|
1074
|
+
PreToolUse path leaves unflagged.
|
|
1061
1075
|
|
|
1062
1076
|
Args:
|
|
1063
1077
|
violation_text: A single violation string emitted by the enforcer.
|
|
@@ -1096,10 +1110,10 @@ def enclosing_span_range(violation_text: str) -> range | None:
|
|
|
1096
1110
|
|
|
1097
1111
|
Every diff-scoped enforcer check tags its message with an enclosing-unit
|
|
1098
1112
|
span fragment. This dispatcher tries each span extractor from
|
|
1099
|
-
``_all_span_range_extractors
|
|
1100
|
-
check's span through one shared mechanism
|
|
1101
|
-
adding one extractor to that registry rather than threading a new
|
|
1102
|
-
through ``split_violations_by_scope``.
|
|
1113
|
+
``_all_span_range_extractors``, so the gate reconstructs every scoped
|
|
1114
|
+
check's span through one shared mechanism. Adding a new scoped check
|
|
1115
|
+
means adding one extractor to that registry rather than threading a new
|
|
1116
|
+
branch through ``split_violations_by_scope``.
|
|
1103
1117
|
|
|
1104
1118
|
Args:
|
|
1105
1119
|
violation_text: A single violation string emitted by the enforcer.
|
|
@@ -1510,6 +1524,73 @@ def _report_partitioned_violations(
|
|
|
1510
1524
|
return 0
|
|
1511
1525
|
|
|
1512
1526
|
|
|
1527
|
+
def _staged_test_file_paths(repository_root: Path) -> list[Path]:
|
|
1528
|
+
"""Return the staged Python test files that exist under a repository.
|
|
1529
|
+
|
|
1530
|
+
Args:
|
|
1531
|
+
repository_root: The repository root whose staged index is read.
|
|
1532
|
+
|
|
1533
|
+
Returns:
|
|
1534
|
+
The staged paths whose extension is Python, whose name matches a
|
|
1535
|
+
test-file pattern, and which exist on disk.
|
|
1536
|
+
"""
|
|
1537
|
+
all_test_paths: list[Path] = []
|
|
1538
|
+
for each_path in paths_from_git_staged(repository_root):
|
|
1539
|
+
if each_path.suffix != PYTHON_FILE_EXTENSION:
|
|
1540
|
+
continue
|
|
1541
|
+
if is_test_path(str(each_path)) and each_path.is_file():
|
|
1542
|
+
all_test_paths.append(each_path)
|
|
1543
|
+
return all_test_paths
|
|
1544
|
+
|
|
1545
|
+
|
|
1546
|
+
def run_staged_test_files(repository_root: Path) -> int:
|
|
1547
|
+
"""Run pytest over the staged test files and return the gate exit code.
|
|
1548
|
+
|
|
1549
|
+
Args:
|
|
1550
|
+
repository_root: The repository root the staged test files belong to.
|
|
1551
|
+
|
|
1552
|
+
Returns:
|
|
1553
|
+
0 when no test file is staged, when the staged test files collect
|
|
1554
|
+
no tests (pytest's no-tests-collected exit code), or when every staged
|
|
1555
|
+
test passes. pytest's non-zero exit code otherwise, which blocks the
|
|
1556
|
+
commit.
|
|
1557
|
+
"""
|
|
1558
|
+
all_test_paths = _staged_test_file_paths(repository_root)
|
|
1559
|
+
if not all_test_paths:
|
|
1560
|
+
return 0
|
|
1561
|
+
pytest_process = subprocess.run(
|
|
1562
|
+
[
|
|
1563
|
+
sys.executable,
|
|
1564
|
+
*ALL_PYTEST_MODULE_INVOCATION,
|
|
1565
|
+
*[str(each_path) for each_path in all_test_paths],
|
|
1566
|
+
],
|
|
1567
|
+
cwd=str(repository_root),
|
|
1568
|
+
timeout=STAGED_PYTEST_TIMEOUT_SECONDS,
|
|
1569
|
+
check=False,
|
|
1570
|
+
)
|
|
1571
|
+
if pytest_process.returncode == PYTEST_NO_TESTS_COLLECTED_EXIT_CODE:
|
|
1572
|
+
return 0
|
|
1573
|
+
if pytest_process.returncode != 0:
|
|
1574
|
+
print(STAGED_TEST_FAILURE_HEADER, file=sys.stderr)
|
|
1575
|
+
return pytest_process.returncode
|
|
1576
|
+
|
|
1577
|
+
|
|
1578
|
+
def _report_terminology_findings(all_findings: list[str]) -> None:
|
|
1579
|
+
"""Print the terminology-sweep findings, when any, to standard error.
|
|
1580
|
+
|
|
1581
|
+
Args:
|
|
1582
|
+
all_findings: The near-miss findings from the staged terminology sweep.
|
|
1583
|
+
"""
|
|
1584
|
+
if not all_findings:
|
|
1585
|
+
return
|
|
1586
|
+
print(
|
|
1587
|
+
TERMINOLOGY_SWEEP_GATE_HEADER.format(finding_count=len(all_findings)),
|
|
1588
|
+
file=sys.stderr,
|
|
1589
|
+
)
|
|
1590
|
+
for each_finding in all_findings:
|
|
1591
|
+
print(f" {each_finding}", file=sys.stderr)
|
|
1592
|
+
|
|
1593
|
+
|
|
1513
1594
|
def parse_arguments(all_arguments: list[str]) -> argparse.Namespace:
|
|
1514
1595
|
"""Parse the command-line arguments for the code-rules gate.
|
|
1515
1596
|
|
|
@@ -1590,6 +1671,10 @@ def main(all_arguments: list[str]) -> int:
|
|
|
1590
1671
|
validate_content, file_paths, repository_root, all_added_lines_by_path=None
|
|
1591
1672
|
)
|
|
1592
1673
|
if arguments.staged:
|
|
1674
|
+
all_terminology_findings = staged_terminology_findings(repository_root)
|
|
1675
|
+
_report_terminology_findings(all_terminology_findings)
|
|
1676
|
+
terminology_exit_code = 1 if all_terminology_findings else 0
|
|
1677
|
+
staged_test_exit_code = run_staged_test_files(repository_root)
|
|
1593
1678
|
staged_file_paths = paths_from_git_staged(repository_root)
|
|
1594
1679
|
staged_file_paths = filter_paths_under_prefixes(
|
|
1595
1680
|
staged_file_paths,
|
|
@@ -1597,17 +1682,18 @@ def main(all_arguments: list[str]) -> int:
|
|
|
1597
1682
|
arguments.only_under,
|
|
1598
1683
|
)
|
|
1599
1684
|
if not staged_file_paths:
|
|
1600
|
-
return
|
|
1685
|
+
return terminology_exit_code or staged_test_exit_code
|
|
1601
1686
|
staged_added_lines = added_lines_by_file_staged(
|
|
1602
1687
|
repository_root, staged_file_paths
|
|
1603
1688
|
)
|
|
1604
|
-
|
|
1689
|
+
gate_exit_code = run_gate(
|
|
1605
1690
|
validate_content,
|
|
1606
1691
|
staged_file_paths,
|
|
1607
1692
|
repository_root,
|
|
1608
1693
|
all_added_lines_by_path=staged_added_lines,
|
|
1609
1694
|
read_staged_content_flag=True,
|
|
1610
1695
|
)
|
|
1696
|
+
return gate_exit_code or terminology_exit_code or staged_test_exit_code
|
|
1611
1697
|
file_paths = paths_from_git_diff(repository_root, arguments.base)
|
|
1612
1698
|
file_paths = filter_paths_under_prefixes(
|
|
1613
1699
|
file_paths,
|
|
@@ -16,6 +16,7 @@ Named constants for every script in `_shared/pr-loop/scripts/`. Each module owns
|
|
|
16
16
|
| `preflight_self_heal_constants.py` | `preflight_self_heal.py` — git config keys and local-scope detection strings |
|
|
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
|
+
| `terminology_sweep_constants.py` | `terminology_sweep.py` — identifier and prose-token regexes, diff-parsing prefixes, code-file extensions, and the finding-message template |
|
|
19
20
|
| `__init__.py` | Empty package marker |
|
|
20
21
|
|
|
21
22
|
## Convention
|
|
@@ -80,3 +80,16 @@ ALL_GIT_DIFF_NAME_ONLY_NULL_TERMINATED_COMMAND_PREFIX: tuple[str, ...] = (
|
|
|
80
80
|
"--name-only",
|
|
81
81
|
"-z",
|
|
82
82
|
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
ALL_PYTEST_MODULE_INVOCATION: tuple[str, ...] = (
|
|
86
|
+
"-m",
|
|
87
|
+
"pytest",
|
|
88
|
+
"-q",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
STAGED_PYTEST_TIMEOUT_SECONDS: int = 600
|
|
92
|
+
|
|
93
|
+
STAGED_TEST_FAILURE_HEADER: str = (
|
|
94
|
+
"code_rules_gate: staged test file(s) failed under pytest; commit blocked."
|
|
95
|
+
)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Constants for the commit-time terminology sweep."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
ALL_SWEEP_CODE_FILE_EXTENSIONS: frozenset[str] = frozenset(
|
|
6
|
+
{".py", ".mjs", ".js", ".ts"}
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
MARKDOWN_FILE_EXTENSION: str = ".md"
|
|
10
|
+
|
|
11
|
+
SNAKE_CASE_IDENTIFIER_PATTERN: re.Pattern[str] = re.compile(
|
|
12
|
+
r"\b[a-z][a-z0-9]*(?:_[a-z0-9]+)+\b"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
CAMEL_CASE_IDENTIFIER_PATTERN: re.Pattern[str] = re.compile(
|
|
16
|
+
r"\b[a-z]+(?:[A-Z][a-z0-9]*)+\b"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
CAMEL_CASE_WORD_PATTERN: re.Pattern[str] = re.compile(
|
|
20
|
+
r"[A-Z]+(?=[A-Z][a-z])|[A-Z]?[a-z]+|[A-Z]+|[0-9]+"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
HYPHENATED_PROSE_TOKEN_PATTERN: re.Pattern[str] = re.compile(
|
|
24
|
+
r"\b[A-Za-z][A-Za-z0-9]*(?:-[A-Za-z][A-Za-z0-9]*)+\b"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
PROSE_WORD_PATTERN: re.Pattern[str] = re.compile(r"[A-Za-z][A-Za-z0-9]*")
|
|
28
|
+
|
|
29
|
+
STRING_LITERAL_CONTENT_PATTERN: re.Pattern[str] = re.compile(
|
|
30
|
+
r"\"([^\"]*)\"|'([^']*)'|`([^`]*)`"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
DIFF_FILE_HEADER_PREFIX: str = "+++ "
|
|
34
|
+
|
|
35
|
+
DIFF_HUNK_HEADER_PATTERN: re.Pattern[str] = re.compile(
|
|
36
|
+
r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
DIFF_ADDED_LINE_PREFIX: str = "+"
|
|
40
|
+
|
|
41
|
+
DIFF_REMOVED_LINE_PREFIX: str = "-"
|
|
42
|
+
|
|
43
|
+
DIFF_NEW_FILE_HEADER_PREFIX: str = "+++"
|
|
44
|
+
|
|
45
|
+
DIFF_OLD_FILE_HEADER_PREFIX: str = "---"
|
|
46
|
+
|
|
47
|
+
ALL_DIFF_FILE_PATH_STRIP_PREFIXES: tuple[str, ...] = ("a/", "b/")
|
|
48
|
+
|
|
49
|
+
PYTHON_COMMENT_MARKER: str = "#"
|
|
50
|
+
|
|
51
|
+
JAVASCRIPT_LINE_COMMENT_MARKER: str = "//"
|
|
52
|
+
|
|
53
|
+
JSDOC_CONTINUATION_MARKER: str = "*"
|
|
54
|
+
|
|
55
|
+
MINIMUM_IDENTIFIER_TOKEN_COUNT: int = 2
|
|
56
|
+
|
|
57
|
+
ALL_COMMON_ENGLISH_COMPOUND_TAIL_WORDS: frozenset[str] = frozenset(
|
|
58
|
+
{
|
|
59
|
+
"only",
|
|
60
|
+
"driven",
|
|
61
|
+
"safe",
|
|
62
|
+
"based",
|
|
63
|
+
"level",
|
|
64
|
+
"quality",
|
|
65
|
+
"known",
|
|
66
|
+
"source",
|
|
67
|
+
"party",
|
|
68
|
+
"case",
|
|
69
|
+
"time",
|
|
70
|
+
"wide",
|
|
71
|
+
"free",
|
|
72
|
+
"friendly",
|
|
73
|
+
"specific",
|
|
74
|
+
"aware",
|
|
75
|
+
"ready",
|
|
76
|
+
"side",
|
|
77
|
+
"grade",
|
|
78
|
+
"oriented",
|
|
79
|
+
"related",
|
|
80
|
+
"sensitive",
|
|
81
|
+
"facing",
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
TERMINOLOGY_FINDING_TEMPLATE: str = (
|
|
86
|
+
"{file_path}:{line_number}: prose term '{candidate}' near-misses code "
|
|
87
|
+
"identifier '{identifier}' (shared prefix token, divergent tail) — align "
|
|
88
|
+
"the prose wording with the identifier"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
TERMINOLOGY_SWEEP_DESCRIPTION: str = (
|
|
92
|
+
"Flag prose terms that near-miss an identifier introduced on added code "
|
|
93
|
+
"lines of a unified diff."
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
DIFF_FILE_ARGUMENT_HELP: str = (
|
|
97
|
+
"Read the unified diff from this file instead of standard input."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
ALL_GIT_DIFF_CACHED_UNIFIED_ZERO_COMMAND: tuple[str, ...] = (
|
|
102
|
+
"git",
|
|
103
|
+
"diff",
|
|
104
|
+
"--cached",
|
|
105
|
+
"--unified=0",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
GIT_DIFF_SUBPROCESS_TIMEOUT_SECONDS: int = 30
|
|
109
|
+
|
|
110
|
+
TERMINOLOGY_SWEEP_GATE_HEADER: str = (
|
|
111
|
+
"terminology_sweep: {finding_count} cross-surface terminology near-miss(es) "
|
|
112
|
+
"on staged prose:"
|
|
113
|
+
)
|