claude-dev-env 2.36.0 → 4.0.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/.agents/skills/_shared/advisor/scripts/README.md +1 -1
- package/.agents/skills/team-advisor/SKILL.md +11 -11
- package/_shared/advisor/advisor-protocol.md +51 -51
- package/_shared/advisor/reference/advisor-block.md +9 -9
- package/_shared/advisor/reference/astra-rung.md +39 -0
- package/_shared/advisor/reference/identity.md +4 -4
- package/_shared/advisor/reference/lifecycle.md +6 -6
- package/_shared/advisor/reference/spawn-walk-log.md +9 -9
- package/_shared/advisor/reference/third-party-bind.md +11 -11
- package/_shared/advisor/reference/warm-up.md +13 -13
- package/_shared/advisor/scripts/codex_astra_advisor.py +308 -0
- package/_shared/advisor/scripts/codex_astra_preflight.py +115 -0
- package/_shared/advisor/scripts/codex_astra_reply.py +159 -0
- package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +3 -3
- package/_shared/advisor/scripts/config/advisor_scripts_constants/astra_advisor_constants.py +38 -0
- package/_shared/advisor/scripts/model_tier_run_validator.py +17 -18
- package/_shared/advisor/scripts/tests/test_codex_astra_advisor.py +271 -0
- package/_shared/advisor/scripts/tests/test_codex_astra_preflight.py +24 -0
- package/_shared/advisor/scripts/tests/test_codex_astra_reply.py +42 -0
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +55 -30
- package/_shared/advisor/scripts/tests/test_tier_model_ids.py +14 -4
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +14 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +138 -50
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +13 -1
- package/docs/references/team-advisor-skill.md +3 -3
- package/hooks/validators/README.md +35 -1
- package/package.json +1 -1
- package/scripts/check.ps1 +18 -6
- package/scripts/policy_lint/adapter_detectors.py +42 -2
- package/scripts/policy_lint/adapters.py +24 -0
- package/scripts/policy_lint/config/constants.py +11 -0
- package/scripts/policy_lint/engine.py +1 -1
- package/scripts/policy_lint/model.py +1 -0
- package/scripts/policy_lint/registry.py +10 -1
- package/scripts/repository_checks/__init__.py +1 -0
- package/scripts/repository_checks/claude_md.py +108 -0
- package/scripts/repository_checks/config/__init__.py +1 -0
- package/scripts/repository_checks/config/constants.py +150 -0
- package/scripts/repository_checks/env_var_documentation.py +243 -0
- package/scripts/repository_checks/hook_modules.py +34 -0
- package/scripts/repository_checks/models.py +37 -0
- package/scripts/repository_checks/package_inventory.py +118 -0
- package/scripts/repository_checks/pytest_testpaths.py +84 -0
- package/scripts/repository_checks/reporting.py +39 -0
- package/scripts/repository_checks/runner.py +93 -0
- package/scripts/repository_checks/tracked_secrets.py +147 -0
- package/scripts/repository_checks/tracked_tree.py +28 -0
- package/scripts/repository_policy.py +120 -0
- package/scripts/tests/repository_policy_test_support.py +89 -0
- package/scripts/tests/test_cde_lint.py +25 -1
- package/scripts/tests/test_claude_md.py +68 -0
- package/scripts/tests/test_env_var_documentation.py +133 -0
- package/scripts/tests/test_hook_modules.py +17 -0
- package/scripts/tests/test_models.py +26 -0
- package/scripts/tests/test_package_inventory.py +96 -0
- package/scripts/tests/test_policy_lint_rules_registry.py +117 -0
- package/scripts/tests/test_pytest_testpaths.py +66 -0
- package/scripts/tests/test_reporting.py +54 -0
- package/scripts/tests/test_repository_policy.py +71 -0
- package/scripts/tests/test_runner.py +59 -0
- package/scripts/tests/test_tracked_secrets.py +175 -0
- package/scripts/tests/test_tracked_tree.py +28 -0
- package/_shared/advisor/reference/sol-rung.md +0 -39
- package/_shared/advisor/scripts/codex_sol_advisor.py +0 -581
- package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +0 -38
- package/_shared/advisor/scripts/tests/test_codex_sol_advisor.py +0 -740
|
@@ -105,10 +105,19 @@ def test_sendmessage_reply_wait_is_positive_bound() -> None:
|
|
|
105
105
|
assert ADVISOR_SENDMESSAGE_REPLY_WAIT_SECONDS == 120
|
|
106
106
|
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
@pytest.mark.parametrize("tier_name", ["Astra", "astra", "ASTRA", " Astra "])
|
|
109
|
+
def test_resolve_codex_model_id_maps_astra(tier_name: str) -> None:
|
|
110
|
+
assert resolve_codex_model_id(tier_name) == "gpt-6-astra"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_resolve_codex_model_id_rejects_old_sol_tier() -> None:
|
|
114
|
+
with pytest.raises(ValueError, match="not a known model tier"):
|
|
115
|
+
resolve_codex_model_id("Sol")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_resolve_cli_model_id_rejects_astra_as_a_claude_alias() -> None:
|
|
119
|
+
with pytest.raises(ValueError, match="not a known model tier"):
|
|
120
|
+
resolve_cli_model_id("Astra")
|
|
112
121
|
|
|
113
122
|
|
|
114
123
|
def test_resolve_codex_model_id_rejects_claude_tier() -> None:
|
|
@@ -132,6 +141,7 @@ def test_cli_model_alias_map_keys_match_known_tiers() -> None:
|
|
|
132
141
|
def test_canonical_tier_name_strips_and_normalizes() -> None:
|
|
133
142
|
assert canonical_tier_name(" opus ") == "Opus"
|
|
134
143
|
assert canonical_tier_name("thirdparty") == "ThirdParty"
|
|
144
|
+
assert canonical_tier_name(" astra ") == "Astra"
|
|
135
145
|
assert canonical_tier_name("") is None
|
|
136
146
|
assert canonical_tier_name("Titan") is None
|
|
137
147
|
|
|
@@ -109,6 +109,20 @@ ALL_GIT_DIFF_CACHED_UNIFIED_ZERO_COMMAND: tuple[str, ...] = (
|
|
|
109
109
|
|
|
110
110
|
GIT_DIFF_SUBPROCESS_TIMEOUT_SECONDS: int = 30
|
|
111
111
|
|
|
112
|
+
GIT_NO_MATCH_EXIT_CODE: int = 1
|
|
113
|
+
|
|
114
|
+
GIT_OPERATION_FAILURE_TEMPLATE: str = "{operation} failed: {details}"
|
|
115
|
+
|
|
116
|
+
GIT_DIFF_OPERATION: str = "git diff --cached"
|
|
117
|
+
|
|
118
|
+
GIT_GREP_OPERATION_TEMPLATE: str = "git grep for {identifier}"
|
|
119
|
+
|
|
120
|
+
GIT_FAILURE_WITHOUT_DETAILS: str = "no diagnostic text"
|
|
121
|
+
|
|
122
|
+
GIT_TEXT_ENCODING: str = "utf-8"
|
|
123
|
+
|
|
124
|
+
GIT_DECODE_ERRORS: str = "replace"
|
|
125
|
+
|
|
112
126
|
TERMINOLOGY_SWEEP_GATE_HEADER: str = (
|
|
113
127
|
"terminology_sweep: {finding_count} cross-surface terminology near-miss(es) "
|
|
114
128
|
"on staged prose:"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Flag a prose term that names a code identifier a hair differently.
|
|
2
2
|
|
|
3
3
|
A change adds the field ``premium_request_interactions`` while the same
|
|
4
|
-
branch's docs call it the ``premium-request-budget`` field
|
|
4
|
+
branch's docs call it the ``premium-request-budget`` field. One thing, two
|
|
5
5
|
names, and a reader who searches one never finds the other.
|
|
6
6
|
|
|
7
7
|
::
|
|
@@ -14,38 +14,29 @@ names, and a reader who searches one never finds the other.
|
|
|
14
14
|
|
|
15
15
|
The sweep reads a unified diff, collects the multi-word identifiers added on
|
|
16
16
|
code lines, and scans each added prose line for a hyphen or space variant that
|
|
17
|
-
renames only the final word of an identifier
|
|
18
|
-
and differs on the last. A term that diverges in any earlier
|
|
19
|
-
only one leading word, is ordinary prose. It also spares
|
|
20
|
-
tails, singular/plural forms, stopword windows, and words
|
|
21
|
-
tokens of another introduced identifier.
|
|
17
|
+
renames only the final word of an identifier. The variant agrees on every
|
|
18
|
+
earlier word and differs on the last. A term that diverges in any earlier
|
|
19
|
+
word, or shares only one leading word, is ordinary prose. It also spares
|
|
20
|
+
English compound tails, singular/plural forms, stopword windows, and words
|
|
21
|
+
that are themselves tokens of another introduced identifier.
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
24
|
import argparse
|
|
25
25
|
import os
|
|
26
26
|
import subprocess
|
|
27
27
|
import sys
|
|
28
|
+
from collections.abc import Sequence
|
|
28
29
|
from pathlib import Path
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
if parent_directory not in sys.path:
|
|
32
|
-
sys.path.insert(0, parent_directory)
|
|
33
|
-
|
|
34
|
-
from pr_loop_shared_constants.terminology_sweep_constants import ( # noqa: E402
|
|
31
|
+
from pr_loop_shared_constants.terminology_sweep_constants import (
|
|
35
32
|
ALL_COMMON_ENGLISH_COMPOUND_TAIL_WORDS,
|
|
33
|
+
ALL_DIFF_FILE_PATH_STRIP_PREFIXES,
|
|
34
|
+
ALL_GIT_DIFF_CACHED_UNIFIED_ZERO_COMMAND,
|
|
36
35
|
ALL_GIT_GREP_BASE_TREE_COMMAND_PREFIX,
|
|
37
36
|
ALL_PROSE_STOPWORD_TOKENS,
|
|
38
37
|
ALL_STRING_ESCAPE_SEQUENCES,
|
|
39
|
-
ALL_TEST_FILE_NAME_INFIX_MARKERS,
|
|
40
|
-
GIT_BASE_TREE_REVISION,
|
|
41
|
-
IDENTIFIER_TOKEN_SEPARATOR,
|
|
42
|
-
PROSE_WINDOW_WORD_SEPARATOR,
|
|
43
|
-
TEST_DIRECTORY_PATH_SEGMENT,
|
|
44
|
-
TEST_FILE_PREFIX,
|
|
45
|
-
TEST_FILE_SUFFIX,
|
|
46
|
-
ALL_DIFF_FILE_PATH_STRIP_PREFIXES,
|
|
47
|
-
ALL_GIT_DIFF_CACHED_UNIFIED_ZERO_COMMAND,
|
|
48
38
|
ALL_SWEEP_CODE_FILE_EXTENSIONS,
|
|
39
|
+
ALL_TEST_FILE_NAME_INFIX_MARKERS,
|
|
49
40
|
CAMEL_CASE_IDENTIFIER_PATTERN,
|
|
50
41
|
CAMEL_CASE_WORD_PATTERN,
|
|
51
42
|
DIFF_ADDED_LINE_PREFIX,
|
|
@@ -55,19 +46,32 @@ from pr_loop_shared_constants.terminology_sweep_constants import ( # noqa: E402
|
|
|
55
46
|
DIFF_NEW_FILE_HEADER_PREFIX,
|
|
56
47
|
DIFF_OLD_FILE_HEADER_PREFIX,
|
|
57
48
|
DIFF_REMOVED_LINE_PREFIX,
|
|
49
|
+
GIT_BASE_TREE_REVISION,
|
|
50
|
+
GIT_DECODE_ERRORS,
|
|
51
|
+
GIT_DIFF_OPERATION,
|
|
58
52
|
GIT_DIFF_SUBPROCESS_TIMEOUT_SECONDS,
|
|
53
|
+
GIT_FAILURE_WITHOUT_DETAILS,
|
|
54
|
+
GIT_GREP_OPERATION_TEMPLATE,
|
|
55
|
+
GIT_NO_MATCH_EXIT_CODE,
|
|
56
|
+
GIT_OPERATION_FAILURE_TEMPLATE,
|
|
57
|
+
GIT_TEXT_ENCODING,
|
|
59
58
|
HYPHENATED_PROSE_TOKEN_PATTERN,
|
|
59
|
+
IDENTIFIER_TOKEN_SEPARATOR,
|
|
60
60
|
INLINE_CODE_SPAN_PATTERN,
|
|
61
61
|
JAVASCRIPT_LINE_COMMENT_MARKER,
|
|
62
62
|
JSDOC_CONTINUATION_MARKER,
|
|
63
63
|
MARKDOWN_FILE_EXTENSION,
|
|
64
64
|
MINIMUM_IDENTIFIER_TOKEN_COUNT,
|
|
65
|
+
PROSE_WINDOW_WORD_SEPARATOR,
|
|
65
66
|
PROSE_WORD_PATTERN,
|
|
66
67
|
PYTHON_COMMENT_MARKER,
|
|
67
68
|
SNAKE_CASE_IDENTIFIER_PATTERN,
|
|
68
69
|
STRING_LITERAL_CONTENT_PATTERN,
|
|
69
70
|
TERMINOLOGY_FINDING_TEMPLATE,
|
|
70
71
|
TERMINOLOGY_SWEEP_DESCRIPTION,
|
|
72
|
+
TEST_DIRECTORY_PATH_SEGMENT,
|
|
73
|
+
TEST_FILE_PREFIX,
|
|
74
|
+
TEST_FILE_SUFFIX,
|
|
71
75
|
)
|
|
72
76
|
|
|
73
77
|
IdentifierTuple = tuple[str, ...]
|
|
@@ -173,8 +177,8 @@ def _prose_fragments(file_path: str, line_text: str) -> list[str]:
|
|
|
173
177
|
A Markdown line is prose with its inline-code spans removed, since a
|
|
174
178
|
backticked span names code verbatim. A code line contributes its comment
|
|
175
179
|
tail, its JSDoc continuation text, and the contents of its string
|
|
176
|
-
literals. A test module contributes its comment tail and JSDoc text only
|
|
177
|
-
|
|
180
|
+
literals. A test module contributes its comment tail and JSDoc text only.
|
|
181
|
+
Its string literals hold fixture data, not prose.
|
|
178
182
|
|
|
179
183
|
Args:
|
|
180
184
|
file_path: The path the added line belongs to.
|
|
@@ -201,9 +205,9 @@ def _prose_fragments(file_path: str, line_text: str) -> list[str]:
|
|
|
201
205
|
def _is_test_file(file_path: str) -> bool:
|
|
202
206
|
"""Return whether a diff path names a test code module.
|
|
203
207
|
|
|
204
|
-
A test module's string literals hold fixture data
|
|
205
|
-
generated source, file trees
|
|
206
|
-
reads only its comments and JSDoc lines. A ``quota.test.mjs``, a
|
|
208
|
+
A test module's string literals hold fixture data: embedded diffs,
|
|
209
|
+
generated source, and file trees. That is not documentation prose, so the
|
|
210
|
+
sweep reads only its comments and JSDoc lines. A ``quota.test.mjs``, a
|
|
207
211
|
``layout.spec.ts``, and a ``fixtures.py`` under a tests directory are all
|
|
208
212
|
test modules, so every one of these paths counts::
|
|
209
213
|
|
|
@@ -260,8 +264,8 @@ def _string_literal_fragments(line_text: str) -> list[str]:
|
|
|
260
264
|
Escape sequences inside a literal (``\\n``, ``\\t``) are replaced with
|
|
261
265
|
spaces before the words are read, so an escape letter never glues onto a
|
|
262
266
|
neighbouring word as a phantom prose term. A literal left with no
|
|
263
|
-
whitespace after that replacement is an identifier-shaped value
|
|
264
|
-
a key, a path
|
|
267
|
+
whitespace after that replacement is an identifier-shaped value: a UID,
|
|
268
|
+
a key, or a path. It is not prose, and contributes nothing.
|
|
265
269
|
"""
|
|
266
270
|
all_fragments: list[str] = []
|
|
267
271
|
for each_match in STRING_LITERAL_CONTENT_PATTERN.finditer(line_text):
|
|
@@ -413,9 +417,7 @@ def _word_is_identifier_vocabulary(
|
|
|
413
417
|
return True
|
|
414
418
|
if prose_word.endswith("es") and prose_word[:-2] in all_identifier_tokens:
|
|
415
419
|
return True
|
|
416
|
-
|
|
417
|
-
return True
|
|
418
|
-
return False
|
|
420
|
+
return prose_word.endswith("ies") and prose_word[:-3] + "y" in all_identifier_tokens
|
|
419
421
|
|
|
420
422
|
|
|
421
423
|
def _near_miss_identifier(
|
|
@@ -525,22 +527,8 @@ def _findings_for_line(
|
|
|
525
527
|
return all_findings
|
|
526
528
|
|
|
527
529
|
|
|
528
|
-
def
|
|
529
|
-
diff_text: str,
|
|
530
|
-
all_preexisting_identifier_tuples: frozenset[IdentifierTuple] = frozenset(),
|
|
531
|
-
) -> list[str]:
|
|
532
|
-
"""Return every terminology near-miss finding for a unified diff.
|
|
533
|
-
|
|
534
|
-
Args:
|
|
535
|
-
diff_text: The unified-diff text to sweep.
|
|
536
|
-
all_preexisting_identifier_tuples: Token tuples of added-line identifiers
|
|
537
|
-
the base tree already names. A pre-existing identifier is not one
|
|
538
|
-
the diff introduces, so no prose is flagged against it. Its
|
|
539
|
-
tokens still count as code vocabulary.
|
|
540
|
-
|
|
541
|
-
Returns:
|
|
542
|
-
One finding string per near-miss term on an added prose line.
|
|
543
|
-
"""
|
|
530
|
+
def _find_terminology_near_misses(
|
|
531
|
+
diff_text: str, all_preexisting_identifier_tuples: frozenset[IdentifierTuple]) -> list[str]:
|
|
544
532
|
all_added_lines = _parse_added_lines(diff_text)
|
|
545
533
|
all_identifier_tuples = _collect_introduced_identifiers(all_added_lines)
|
|
546
534
|
all_identifier_tokens = frozenset(
|
|
@@ -571,6 +559,27 @@ def sweep_diff(
|
|
|
571
559
|
return all_findings
|
|
572
560
|
|
|
573
561
|
|
|
562
|
+
def sweep_diff(
|
|
563
|
+
diff_text: str,
|
|
564
|
+
all_preexisting_identifier_tuples: frozenset[IdentifierTuple] = frozenset(),
|
|
565
|
+
) -> list[str]:
|
|
566
|
+
"""Return every terminology near-miss finding for a unified diff.
|
|
567
|
+
|
|
568
|
+
Args:
|
|
569
|
+
diff_text: The unified-diff text to sweep.
|
|
570
|
+
all_preexisting_identifier_tuples: Token tuples of added-line identifiers
|
|
571
|
+
the base tree already names. A pre-existing identifier is not one
|
|
572
|
+
the diff introduces, so no prose is flagged against it. Its
|
|
573
|
+
tokens still count as code vocabulary.
|
|
574
|
+
|
|
575
|
+
Returns:
|
|
576
|
+
One finding string per near-miss term on an added prose line.
|
|
577
|
+
"""
|
|
578
|
+
return _find_terminology_near_misses(
|
|
579
|
+
diff_text, all_preexisting_identifier_tuples
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
|
|
574
583
|
def repository_environment() -> dict[str, str]:
|
|
575
584
|
"""Return the process environment with every GIT_ variable removed.
|
|
576
585
|
|
|
@@ -619,8 +628,8 @@ def _base_tree_names(
|
|
|
619
628
|
|
|
620
629
|
Each name is looked up as a whole word in the base revision's tree. A
|
|
621
630
|
repository with no commits yet, or a lookup that git cannot run, reports
|
|
622
|
-
the name as absent, so the sweep then treats it as newly introduced
|
|
623
|
-
the behaviour the sweep has with no base-tree check at all.
|
|
631
|
+
the name as absent, so the sweep then treats it as newly introduced.
|
|
632
|
+
That is the behaviour the sweep has with no base-tree check at all.
|
|
624
633
|
|
|
625
634
|
Args:
|
|
626
635
|
repository_root: The repository root the lookups run in.
|
|
@@ -652,11 +661,90 @@ def _base_tree_names(
|
|
|
652
661
|
return frozenset(all_present_names)
|
|
653
662
|
|
|
654
663
|
|
|
664
|
+
def _run_strict_git(
|
|
665
|
+
repository_root: Path, all_arguments: Sequence[str]
|
|
666
|
+
) -> subprocess.CompletedProcess[str]:
|
|
667
|
+
try:
|
|
668
|
+
return subprocess.run(
|
|
669
|
+
list(all_arguments),
|
|
670
|
+
cwd=str(repository_root),
|
|
671
|
+
capture_output=True,
|
|
672
|
+
text=True,
|
|
673
|
+
encoding=GIT_TEXT_ENCODING,
|
|
674
|
+
errors=GIT_DECODE_ERRORS,
|
|
675
|
+
timeout=GIT_DIFF_SUBPROCESS_TIMEOUT_SECONDS,
|
|
676
|
+
check=False,
|
|
677
|
+
env=repository_environment(),
|
|
678
|
+
)
|
|
679
|
+
except (subprocess.TimeoutExpired, OSError) as e:
|
|
680
|
+
raise RuntimeError(str(e)) from e
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
def _raise_for_git_failure(
|
|
684
|
+
git_process: subprocess.CompletedProcess[str], operation: str
|
|
685
|
+
) -> None:
|
|
686
|
+
if git_process.returncode == 0:
|
|
687
|
+
return
|
|
688
|
+
details = git_process.stderr.strip() or git_process.stdout.strip()
|
|
689
|
+
raise RuntimeError(
|
|
690
|
+
GIT_OPERATION_FAILURE_TEMPLATE.format(
|
|
691
|
+
operation=operation,
|
|
692
|
+
details=details or GIT_FAILURE_WITHOUT_DETAILS,
|
|
693
|
+
)
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
def _strict_base_tree_names(
|
|
698
|
+
repository_root: Path, all_names: frozenset[str]
|
|
699
|
+
) -> frozenset[str]:
|
|
700
|
+
all_present_names: set[str] = set()
|
|
701
|
+
for each_name in sorted(all_names):
|
|
702
|
+
all_arguments = (
|
|
703
|
+
*ALL_GIT_GREP_BASE_TREE_COMMAND_PREFIX,
|
|
704
|
+
each_name,
|
|
705
|
+
GIT_BASE_TREE_REVISION,
|
|
706
|
+
)
|
|
707
|
+
grep_process = _run_strict_git(repository_root, all_arguments)
|
|
708
|
+
if grep_process.returncode == 0:
|
|
709
|
+
all_present_names.add(each_name)
|
|
710
|
+
continue
|
|
711
|
+
if grep_process.returncode == GIT_NO_MATCH_EXIT_CODE:
|
|
712
|
+
continue
|
|
713
|
+
operation = GIT_GREP_OPERATION_TEMPLATE.format(identifier=each_name)
|
|
714
|
+
_raise_for_git_failure(grep_process, operation)
|
|
715
|
+
return frozenset(all_present_names)
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
def strict_staged_terminology_findings(repository_root: Path) -> list[str]:
|
|
719
|
+
"""Return near-miss findings for the staged diff.
|
|
720
|
+
|
|
721
|
+
Args:
|
|
722
|
+
repository_root: The repository root the staged diff is read from.
|
|
723
|
+
|
|
724
|
+
Returns:
|
|
725
|
+
One finding string per near-miss term on a staged prose line.
|
|
726
|
+
|
|
727
|
+
Raises:
|
|
728
|
+
RuntimeError: Git could not read the staged diff or look up an
|
|
729
|
+
identifier in HEAD.
|
|
730
|
+
"""
|
|
731
|
+
diff_process = _run_strict_git(
|
|
732
|
+
repository_root, ALL_GIT_DIFF_CACHED_UNIFIED_ZERO_COMMAND
|
|
733
|
+
)
|
|
734
|
+
_raise_for_git_failure(diff_process, GIT_DIFF_OPERATION)
|
|
735
|
+
all_added_names = _identifier_names_on_added_code_lines(diff_process.stdout)
|
|
736
|
+
preexisting_tuples = frozenset(
|
|
737
|
+
_identifier_token_tuple(each_name)
|
|
738
|
+
for each_name in _strict_base_tree_names(repository_root, all_added_names)
|
|
739
|
+
)
|
|
740
|
+
return _find_terminology_near_misses(diff_process.stdout, preexisting_tuples)
|
|
741
|
+
|
|
742
|
+
|
|
655
743
|
def staged_terminology_findings(repository_root: Path) -> list[str]:
|
|
656
744
|
"""Return terminology near-miss findings for a repository's staged diff.
|
|
657
745
|
|
|
658
746
|
An identifier the base tree already names is not one the staged diff
|
|
659
|
-
introduces, so no prose is flagged against it
|
|
747
|
+
introduces, so no prose is flagged against it. Only genuinely new
|
|
660
748
|
identifiers are swept.
|
|
661
749
|
|
|
662
750
|
Args:
|
|
@@ -684,7 +772,7 @@ def staged_terminology_findings(repository_root: Path) -> list[str]:
|
|
|
684
772
|
_identifier_token_tuple(each_name)
|
|
685
773
|
for each_name in _base_tree_names(repository_root, all_added_names)
|
|
686
774
|
)
|
|
687
|
-
return
|
|
775
|
+
return _find_terminology_near_misses(diff_process.stdout, preexisting_tuples)
|
|
688
776
|
|
|
689
777
|
|
|
690
778
|
def _read_diff_text(diff_file_path: str | None) -> str:
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import importlib.util
|
|
4
4
|
import subprocess
|
|
5
|
+
import sys
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from types import ModuleType
|
|
7
8
|
|
|
@@ -10,17 +11,26 @@ import pytest
|
|
|
10
11
|
|
|
11
12
|
def _load_sweep_module() -> ModuleType:
|
|
12
13
|
module_path = Path(__file__).parent.parent / "terminology_sweep.py"
|
|
14
|
+
module_directory = str(module_path.parent)
|
|
15
|
+
should_remove_module_directory = module_directory not in sys.path
|
|
16
|
+
if should_remove_module_directory:
|
|
17
|
+
sys.path.insert(0, module_directory)
|
|
13
18
|
spec = importlib.util.spec_from_file_location("terminology_sweep", module_path)
|
|
14
19
|
assert spec is not None
|
|
15
20
|
assert spec.loader is not None
|
|
16
21
|
module = importlib.util.module_from_spec(spec)
|
|
17
|
-
|
|
22
|
+
try:
|
|
23
|
+
spec.loader.exec_module(module)
|
|
24
|
+
finally:
|
|
25
|
+
if should_remove_module_directory:
|
|
26
|
+
sys.path.remove(module_directory)
|
|
18
27
|
return module
|
|
19
28
|
|
|
20
29
|
|
|
21
30
|
sweep_module = _load_sweep_module()
|
|
22
31
|
sweep_diff = sweep_module.sweep_diff
|
|
23
32
|
staged_terminology_findings = sweep_module.staged_terminology_findings
|
|
33
|
+
strict_staged_terminology_findings = sweep_module.strict_staged_terminology_findings
|
|
24
34
|
main = sweep_module.main
|
|
25
35
|
parse_added_lines = sweep_module._parse_added_lines
|
|
26
36
|
|
|
@@ -282,6 +292,8 @@ def test_staged_terminology_findings_flags_staged_prose(tmp_path: Path) -> None:
|
|
|
282
292
|
env=_hermetic_git_environment(),
|
|
283
293
|
)
|
|
284
294
|
findings = staged_terminology_findings(tmp_path)
|
|
295
|
+
with pytest.raises(RuntimeError, match="git grep"):
|
|
296
|
+
strict_staged_terminology_findings(tmp_path)
|
|
285
297
|
assert any("premium-request" in each_finding for each_finding in findings)
|
|
286
298
|
|
|
287
299
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Team-
|
|
1
|
+
# Team-advisor skill
|
|
2
2
|
|
|
3
|
-
`/team-advisor` binds one standing warm advisor for this session at the strongest reachable tier.
|
|
3
|
+
`/team-advisor` binds one standing warm advisor for this session at the strongest reachable tier. It sends explicit packets to a warm Agent/SendMessage advisor or the read-only `codex_astra_advisor.py` helper.
|
|
4
4
|
|
|
5
5
|
## Refs
|
|
6
6
|
|
|
@@ -17,4 +17,4 @@ Follow the call rules in `advisor-tool.md` (orientation first, then consult befo
|
|
|
17
17
|
|
|
18
18
|
## Selected path
|
|
19
19
|
|
|
20
|
-
`/team-advisor` is the repository's advisor implementation. It provides explicit first-consult packets, delta consults, a standing warm reviewer, an in-session
|
|
20
|
+
`/team-advisor` is the repository's advisor implementation. It provides explicit first-consult packets, delta consults, a standing warm reviewer, an in-session Astra spawn on Codex, and a read-only Astra CLI option.
|
|
@@ -2,7 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
AST-based Python style checks for code quality enforcement.
|
|
4
4
|
|
|
5
|
-
The checks live in `python_style_checks.py
|
|
5
|
+
The checks live in `python_style_checks.py`. Shared source-line splitting, function discovery, and statement classification live in `python_style_helpers.py`. `python_style_import_bootstrap.py` recognizes the repo's sys.path bootstrap-guard idiom so the imports-at-top check does not penalize a file for following it. `fast_save_validators.py` runs the twelve non-Mypy, non-Ruff checks in-process for the Write/Edit save-path gate, with no per-check subprocess.
|
|
6
|
+
|
|
7
|
+
## Production modules
|
|
8
|
+
|
|
9
|
+
| File | Role |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `abbreviation_checks.py` | Abbreviation and single-letter name checks |
|
|
12
|
+
| `code_quality_checks.py` | Function, nesting, and file length checks |
|
|
13
|
+
| `comment_checks.py` | Comment detection for the opt-in diff-aware linter |
|
|
14
|
+
| `exempt_paths.py` | Shared config, test, and hook-infrastructure path exemptions |
|
|
15
|
+
| `fast_save_validators.py` | In-process Write/Edit save-path validator roster |
|
|
16
|
+
| `file_structure_checks.py` | File structure checks for pre-PR validation |
|
|
17
|
+
| `git_checks.py` | Git and GitHub checks for pre-push review |
|
|
18
|
+
| `health_check.py` | Validator availability, dependency, and version checks |
|
|
19
|
+
| `hook_timing_harness.py` | Hook runtime measurement and threshold report |
|
|
20
|
+
| `magic_value_checks.py` | Hardcoded magic-number checks |
|
|
21
|
+
| `mypy_integration.py` | Mypy static type checking integration |
|
|
22
|
+
| `output_formatter.py` | Colored, diff, progress, and JSON validator output |
|
|
23
|
+
| `pr_reference_checks.py` | PR and commit reference checks in comments |
|
|
24
|
+
| `project_roots.py` | Project-root resolution from a path |
|
|
25
|
+
| `pyproject_config_discovery.py` | Walk-up discovery of tool pyproject tables |
|
|
26
|
+
| `python_antipattern_checks.py` | Mutable defaults, bare except, and print checks |
|
|
27
|
+
| `python_style_checks.py` | Import placement, decorator spacing, blank lines, and view naming |
|
|
28
|
+
| `python_style_helpers.py` | Source-line splitting and function-discovery helpers |
|
|
29
|
+
| `python_style_import_bootstrap.py` | sys.path bootstrap-guard recognizer |
|
|
30
|
+
| `react_checks.py` | React class-component and error-boundary checks |
|
|
31
|
+
| `ruff_integration.py` | Ruff lint integration |
|
|
32
|
+
| `run_all_validators.py` | Pre-push validator orchestration and report |
|
|
33
|
+
| `security_checks.py` | Hardcoded-secret, SQL injection, and XSS checks |
|
|
34
|
+
| `system_temporary_roots.py` | System temp-root membership for staged copies |
|
|
35
|
+
| `todo_checks.py` | TODO/FIXME tracking with issue-reference requirement |
|
|
36
|
+
| `type_safety_checks.py` | Missing type hints and Any usage checks |
|
|
37
|
+
| `useless_test_checks.py` | Useless-test detection |
|
|
38
|
+
| `validator_base.py` | Shared validator dataclasses, source read, and parse |
|
|
39
|
+
| `validator_defaults.py` | Shared constants for the validators package |
|
|
6
40
|
|
|
7
41
|
## Checks Implemented
|
|
8
42
|
|
package/package.json
CHANGED
package/scripts/check.ps1
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env pwsh
|
|
2
2
|
<#
|
|
3
3
|
.SYNOPSIS
|
|
4
|
-
One-shot quality gate
|
|
5
|
-
process-tree mypy, and the blocking pytest suite from a
|
|
4
|
+
One-shot quality gate. Runs ruff, hooks mypy, pr-loop mypy,
|
|
5
|
+
process-tree mypy, repository-policy, and the blocking pytest suite from a
|
|
6
|
+
single entry point.
|
|
6
7
|
|
|
7
8
|
.DESCRIPTION
|
|
8
9
|
Resolves paths relative to $PSScriptRoot so the script works from any CWD
|
|
@@ -11,9 +12,10 @@
|
|
|
11
12
|
propagates this file). Tools: ruff over hooks/, mypy over hooks blocking
|
|
12
13
|
and validators, mypy-pr-loop over _shared/pr-loop/scripts production
|
|
13
14
|
modules, mypy-process-tree over the shared process-tree kill helper and
|
|
14
|
-
its constants,
|
|
15
|
-
|
|
16
|
-
script's exit code so CI
|
|
15
|
+
its constants, repository-policy over the committed tree, and optional
|
|
16
|
+
pytest over the blocking enforcer suite. Each tool runs in sequence. The
|
|
17
|
+
first non-zero exit code is preserved as the script's exit code so CI and
|
|
18
|
+
pre-commit can stop on the first failure.
|
|
17
19
|
|
|
18
20
|
.PARAMETER SkipTests
|
|
19
21
|
Skip the pytest run. Useful during local iteration when you want only the
|
|
@@ -25,19 +27,23 @@
|
|
|
25
27
|
.PARAMETER SkipRuff
|
|
26
28
|
Skip the ruff run.
|
|
27
29
|
|
|
30
|
+
.PARAMETER SkipRepositoryPolicy
|
|
31
|
+
Skip the committed-tree repository_policy.py run.
|
|
32
|
+
|
|
28
33
|
.PARAMETER CommentPolicyBase
|
|
29
34
|
Optional git reference used as the comment-policy baseline.
|
|
30
35
|
|
|
31
36
|
.OUTPUTS
|
|
32
37
|
Per-tool status lines on stdout. Final summary line:
|
|
33
38
|
CHECK: OK
|
|
34
|
-
CHECK: FAILED tools=ruff,mypy,mypy-pr-loop,mypy-process-tree,pytest
|
|
39
|
+
CHECK: FAILED tools=ruff,repository-policy,mypy,mypy-pr-loop,mypy-process-tree,pytest
|
|
35
40
|
#>
|
|
36
41
|
[CmdletBinding()]
|
|
37
42
|
param(
|
|
38
43
|
[switch]$SkipTests,
|
|
39
44
|
[switch]$SkipMypy,
|
|
40
45
|
[switch]$SkipRuff,
|
|
46
|
+
[switch]$SkipRepositoryPolicy,
|
|
41
47
|
[string]$CommentPolicyBase
|
|
42
48
|
)
|
|
43
49
|
|
|
@@ -121,6 +127,12 @@ Invoke-Tool -Label 'comment-policy' -Action {
|
|
|
121
127
|
}
|
|
122
128
|
}
|
|
123
129
|
|
|
130
|
+
if (-not $SkipRepositoryPolicy) {
|
|
131
|
+
Invoke-Tool -Label 'repository-policy' -Action {
|
|
132
|
+
python (Join-Path $PSScriptRoot 'repository_policy.py') --repository-root $repositoryRoot
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
124
136
|
if (-not $SkipMypy) {
|
|
125
137
|
Invoke-Tool -Label 'mypy' -Action {
|
|
126
138
|
Push-Location $hooksRoot
|
|
@@ -3,11 +3,51 @@ from __future__ import annotations
|
|
|
3
3
|
from collections.abc import Iterable
|
|
4
4
|
from contextlib import redirect_stderr, redirect_stdout
|
|
5
5
|
from io import StringIO
|
|
6
|
-
from pathlib import Path
|
|
6
|
+
from pathlib import Path, PurePosixPath
|
|
7
7
|
|
|
8
8
|
from . import adapter_support
|
|
9
9
|
from .config import constants
|
|
10
|
-
from .model import Diagnostic, Document
|
|
10
|
+
from .model import Diagnostic, Document, DocumentSet, Location, SelectionKind, Severity
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _terminology_diagnostic(finding: str) -> Diagnostic:
|
|
14
|
+
finding_match = constants.TERMINOLOGY_FINDING_PATTERN.fullmatch(finding)
|
|
15
|
+
if finding_match is None:
|
|
16
|
+
raise ValueError(finding)
|
|
17
|
+
return Diagnostic(
|
|
18
|
+
constants.TERMINOLOGY_SWEEP_RULE_ID,
|
|
19
|
+
Severity.ERROR,
|
|
20
|
+
finding_match.group(constants.TERMINOLOGY_MESSAGE_GROUP),
|
|
21
|
+
Location(
|
|
22
|
+
PurePosixPath(finding_match.group(constants.TERMINOLOGY_PATH_GROUP)),
|
|
23
|
+
int(finding_match.group(constants.TERMINOLOGY_LINE_NUMBER_GROUP)),
|
|
24
|
+
),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def terminology_diagnostics(
|
|
29
|
+
document_set: DocumentSet,
|
|
30
|
+
load_module: adapter_support.HookModuleLoader,
|
|
31
|
+
) -> tuple[Diagnostic, ...]:
|
|
32
|
+
"""Report staged prose that near-misses a newly introduced identifier.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
document_set: Candidate staged documents and repository root.
|
|
36
|
+
load_module: Shared script module loader.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Diagnostics that name the prose file and line.
|
|
40
|
+
"""
|
|
41
|
+
if document_set.selection is not SelectionKind.STAGED:
|
|
42
|
+
return ()
|
|
43
|
+
terminology_module = load_module(constants.TERMINOLOGY_SWEEP_MODULE_NAME)
|
|
44
|
+
helper_stdout = StringIO()
|
|
45
|
+
helper_stderr = StringIO()
|
|
46
|
+
with redirect_stdout(helper_stdout), redirect_stderr(helper_stderr):
|
|
47
|
+
all_findings = terminology_module.strict_staged_terminology_findings(
|
|
48
|
+
document_set.repository_root
|
|
49
|
+
)
|
|
50
|
+
return tuple(_terminology_diagnostic(each_finding) for each_finding in all_findings)
|
|
11
51
|
|
|
12
52
|
|
|
13
53
|
def _stable_validator_message(document: Document, raw_message: str) -> str:
|
|
@@ -23,6 +23,16 @@ def _hooks_module(module_name: str) -> ModuleType:
|
|
|
23
23
|
return importlib.import_module(module_name)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def _pr_loop_script_module(module_name: str) -> ModuleType:
|
|
27
|
+
package_directory = Path(__file__).resolve().parent.parent.parent
|
|
28
|
+
scripts_directory = str(
|
|
29
|
+
package_directory.joinpath(*constants.ALL_PR_LOOP_SCRIPTS_PATH_SEGMENTS)
|
|
30
|
+
)
|
|
31
|
+
if scripts_directory not in sys.path:
|
|
32
|
+
sys.path.insert(0, scripts_directory)
|
|
33
|
+
return importlib.import_module(module_name)
|
|
34
|
+
|
|
35
|
+
|
|
26
36
|
def validator_diagnostics(
|
|
27
37
|
document: Document, repository_root: Path
|
|
28
38
|
) -> tuple[Diagnostic, ...]:
|
|
@@ -222,6 +232,20 @@ def test_pairing_diagnostics(document_set: DocumentSet) -> tuple[Diagnostic, ...
|
|
|
222
232
|
return adapter_pairing.test_pairing_diagnostics(document_set, _hooks_module)
|
|
223
233
|
|
|
224
234
|
|
|
235
|
+
def terminology_diagnostics(document_set: DocumentSet) -> tuple[Diagnostic, ...]:
|
|
236
|
+
"""Report staged prose that near-misses a newly introduced identifier.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
document_set: Candidate staged documents and repository root.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
Diagnostics that name the prose file and line.
|
|
243
|
+
"""
|
|
244
|
+
return adapter_detectors.terminology_diagnostics(
|
|
245
|
+
document_set, _pr_loop_script_module
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
|
|
225
249
|
def hook_format_diagnostics(
|
|
226
250
|
document: Document, repository_root: Path
|
|
227
251
|
) -> tuple[Diagnostic, ...]:
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
1
3
|
INCOMPLETE_EXIT_CODE = 3
|
|
2
4
|
INVALID_INPUT_EXIT_CODE = 2
|
|
3
5
|
GIT_EXECUTABLE = "git"
|
|
@@ -64,3 +66,12 @@ FAST_SAVE_VALIDATORS_STEM = "fast_save_validators"
|
|
|
64
66
|
ALL_OVERLAPPING_VALIDATOR_NAMES = frozenset(
|
|
65
67
|
{"Python Style", "Magic Values", "Type Safety", "Test Safety", "React"}
|
|
66
68
|
)
|
|
69
|
+
ALL_PR_LOOP_SCRIPTS_PATH_SEGMENTS = ("_shared", "pr-loop", "scripts")
|
|
70
|
+
TERMINOLOGY_SWEEP_MODULE_NAME = "terminology_sweep"
|
|
71
|
+
TERMINOLOGY_SWEEP_RULE_ID = "terminology-sweep"
|
|
72
|
+
TERMINOLOGY_FINDING_PATTERN = re.compile(
|
|
73
|
+
r"^(?P<path>.+):(?P<line_number>\d+): (?P<message>.+)$"
|
|
74
|
+
)
|
|
75
|
+
TERMINOLOGY_PATH_GROUP = "path"
|
|
76
|
+
TERMINOLOGY_LINE_NUMBER_GROUP = "line_number"
|
|
77
|
+
TERMINOLOGY_MESSAGE_GROUP = "message"
|
|
@@ -180,7 +180,7 @@ def _run_changeset_rule(
|
|
|
180
180
|
all_diagnostics: list[Diagnostic],
|
|
181
181
|
all_skipped_rules: list[str],
|
|
182
182
|
) -> None:
|
|
183
|
-
if document_set.selection not in
|
|
183
|
+
if document_set.selection not in rule.selections:
|
|
184
184
|
all_skipped_rules.append(rule.rule_id)
|
|
185
185
|
return
|
|
186
186
|
all_diagnostics.extend(rule.check(document_set))
|