claude-dev-env 8.6.1 → 8.8.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/privacy-hygiene/reference/sweep-procedure.md +1 -1
- package/.agents/skills/team-advisor/SKILL.md +2 -2
- package/_shared/advisor/reference/identity.md +1 -1
- package/_shared/advisor/reference/spawn-walk-log.md +1 -1
- package/_shared/advisor/reference/warm-up.md +3 -1
- package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +16 -0
- package/_shared/advisor/scripts/model_tier_run_validator.py +23 -5
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +85 -6
- package/_shared/pr-loop/scripts/terminology_sweep.py +10 -9
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +13 -0
- package/bin/install.test.mjs +25 -0
- package/docs/high-trust-agent-delivery.md +132 -0
- package/docs/references/advisor-tool.md +3 -2
- package/docs/second-claude-account.md +50 -0
- package/hooks/advisory/refactor_guard.py +2 -2
- package/hooks/advisory/test_refactor_guard.py +21 -0
- package/hooks/blocking/code_rules_enforcer_test_support.py +10 -0
- package/hooks/blocking/code_rules_shared.py +4 -7
- package/hooks/blocking/conftest.py +22 -0
- package/hooks/blocking/state_description_blocker.py +10 -8
- package/hooks/blocking/test_code_rules_enforcer_duplicate_body.py +2 -11
- package/hooks/blocking/test_code_rules_enforcer_duplicate_body_hook_routing.py +4 -29
- package/hooks/blocking/test_code_rules_enforcer_test_support.py +21 -0
- package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias_hook_routing.py +3 -19
- package/hooks/blocking/test_code_rules_shared.py +18 -0
- package/hooks/blocking/test_conftest.py +35 -0
- package/hooks/blocking/test_pii_scanner.py +8 -0
- package/hooks/blocking/test_state_description_blocker.py +16 -0
- package/hooks/git-hooks/git_hooks_constants/__init__.py +1 -14
- package/hooks/git-hooks/pull_request_handoff.py +2 -2
- package/hooks/git-hooks/test_config.py +10 -3
- package/hooks/git-hooks/test_pull_request_handoff.py +49 -0
- package/hooks/git-hooks/test_verification_notice_state.py +50 -0
- package/hooks/git-hooks/verification_notice_state.py +3 -1
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +13 -5
- package/hooks/hooks_constants/pii_prevention_constants.py +1 -0
- package/hooks/hooks_constants/refactor_guard_constants.py +2 -0
- package/hooks/hooks_constants/session_edit_stage_gate_constants.py +3 -4
- package/hooks/hooks_constants/state_description_blocker_constants.py +0 -3
- package/hooks/hooks_constants/test_state_description_blocker_constants.py +9 -7
- package/hooks/validators/exempt_paths.py +3 -18
- package/hooks/validators/test_exempt_paths.py +20 -0
- package/hooks/validators/test_python_antipattern_checks.py +14 -0
- package/package.json +1 -1
- package/rules/agent-merges-its-own-green-pull-request.md +54 -0
- package/rules/archiving-agent-config.md +10 -0
- package/rules/code-standards.md +1 -0
- package/rules/correction-lens.md +134 -0
- package/rules/flag-non-breaking-findings.md +11 -0
- package/rules/long-horizon-autonomy.md +11 -0
- package/rules/no-contrast-framing.md +68 -0
- package/rules/review-closure-is-a-check.md +44 -0
- package/scripts/Invoke-PesterSuites.ps1 +42 -0
- package/scripts/agent_merge_check.py +381 -0
- package/scripts/check.ps1 +12 -1
- package/scripts/ci/install-playtest.mjs +291 -0
- package/scripts/ci/install-playtest.test.mjs +84 -0
- package/scripts/ci/installer-lifecycle.mjs +11 -30
- package/scripts/ci/installer-lifecycle.test.mjs +14 -0
- package/scripts/ci/pstack-command-shim.test.mjs +33 -0
- package/scripts/ci/scratch-home-install.mjs +73 -0
- package/scripts/ci/scratch-home-install.test.mjs +49 -0
- package/scripts/claude_account_choice.py +246 -0
- package/scripts/claude_account_profile.py +371 -0
- package/scripts/claude_chain_usage.py +49 -5
- package/scripts/codex_compat_materializer.py +33 -16
- package/scripts/contrast_framing.py +101 -0
- package/scripts/dev_env_scripts_constants/agent_merge_check_constants.py +121 -0
- package/scripts/dev_env_scripts_constants/claude_account_constants.py +166 -0
- package/scripts/dev_env_scripts_constants/contrast_framing_constants.py +82 -0
- package/scripts/dev_env_scripts_constants/merge_gate_check_constants.py +23 -0
- package/scripts/dev_env_scripts_constants/review_closure_constants.py +241 -0
- package/scripts/dev_env_scripts_constants/test_claude_account_constants.py +21 -0
- package/scripts/dev_env_scripts_constants/test_code_review_constants.py +12 -0
- package/scripts/dev_env_scripts_constants/test_contrast_framing_constants.py +60 -0
- package/scripts/durable_post_lint.py +30 -4
- package/scripts/followup_cli.py +6 -10
- package/scripts/merge_gate_checks.py +236 -0
- package/scripts/policy_lint/adapter_contrast_framing.py +59 -0
- package/scripts/policy_lint/adapters.py +30 -0
- package/scripts/policy_lint/config/constants.py +2 -0
- package/scripts/policy_lint/registry.py +5 -0
- package/scripts/repository_checks/config/constants.py +12 -0
- package/scripts/repository_checks/config/policy_document.py +28 -2
- package/scripts/repository_checks/followups.py +54 -0
- package/scripts/repository_checks/match_exemptions.py +67 -0
- package/scripts/repository_checks/models.py +46 -2
- package/scripts/repository_checks/reporting.py +23 -7
- package/scripts/repository_checks/tracked_secrets.py +2 -2
- package/scripts/repository_policy.py +4 -2
- package/scripts/review_closure.py +134 -0
- package/scripts/review_closure_github.py +345 -0
- package/scripts/review_closure_model.py +360 -0
- package/scripts/test_agent_merge_check.py +317 -0
- package/scripts/test_claude_account_choice.py +217 -0
- package/scripts/test_claude_account_profile.py +220 -0
- package/scripts/test_claude_chain_usage.py +40 -0
- package/scripts/test_durable_post_lint.py +46 -0
- package/scripts/test_followup_cli.py +40 -0
- package/scripts/test_merge_gate_checks.py +92 -0
- package/scripts/tests/test_code_review_constants.py +78 -0
- package/scripts/tests/test_codex_compat_materializer.py +48 -4
- package/scripts/tests/test_contrast_framing.py +117 -0
- package/scripts/tests/test_followups.py +88 -0
- package/scripts/tests/test_match_exemptions.py +291 -0
- package/scripts/tests/test_models.py +67 -2
- package/scripts/tests/test_package_inventory.py +6 -4
- package/scripts/tests/test_pester_suites_run.py +53 -0
- package/scripts/tests/test_policy_document.py +39 -1
- package/scripts/tests/test_policy_lint_rules_contrast_framing.py +68 -0
- package/scripts/tests/test_reporting.py +18 -0
- package/scripts/tests/test_repository_policy.py +43 -1
- package/scripts/tests/test_review_closure.py +126 -0
- package/scripts/tests/test_review_closure_github.py +240 -0
- package/scripts/tests/test_review_closure_model.py +299 -0
- package/scripts/tests/test_tracked_secrets.py +42 -0
- package/hooks/hooks_constants/precommit_code_rules_gate_constants.py +0 -12
- package/scripts/repository_checks/email_exemptions.py +0 -39
- package/scripts/tests/test_email_exemptions.py +0 -158
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""Constants for the session-edit stage gate hook trio.
|
|
2
2
|
|
|
3
3
|
Shared by the PostToolUse tracker
|
|
4
|
-
(``hooks/observability/session_file_edit_tracker.py``)
|
|
5
|
-
(``hooks/
|
|
6
|
-
|
|
4
|
+
(``hooks/observability/session_file_edit_tracker.py``) and the SessionStart
|
|
5
|
+
cleanup (``hooks/session/session_edit_tracker_cleanup.py``): the per-session
|
|
6
|
+
tracker
|
|
7
7
|
filename shape and JSON payload key, the session-id sanitize pattern, the
|
|
8
8
|
SessionStart source key and the fresh-startup source value the cleanup keys its
|
|
9
9
|
deletion on, the edit-tool name set, the per-session lock filename suffix and
|
|
@@ -62,7 +62,6 @@ ALL_TRACKED_UNSTAGED_FILES_COMMAND: tuple[str, ...] = (
|
|
|
62
62
|
|
|
63
63
|
PARTIAL_COMMIT_BYPASS_MARKER: str = "# partial-commit"
|
|
64
64
|
COMMIT_SUBCOMMAND_TOKEN: str = "commit"
|
|
65
|
-
GIT_EXECUTABLE_TOKEN: str = "git"
|
|
66
65
|
ENV_ASSIGNMENT_PREFIX_PATTERN: re.Pattern[str] = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*=")
|
|
67
66
|
ALL_STAGING_SUBCOMMAND_TOKENS: frozenset[str] = frozenset({"add", "stage"})
|
|
68
67
|
ALL_STAGE_ALL_ADD_FLAG_TOKENS: frozenset[str] = frozenset({"-A", "--all", "-u", "--update"})
|
|
@@ -40,9 +40,6 @@ ALL_COMMENT_TRANSITION_PATTERNS: list[Pattern[str]] = [
|
|
|
40
40
|
compile(r"\bas of\b", IGNORECASE),
|
|
41
41
|
]
|
|
42
42
|
|
|
43
|
-
BLOCK_COMMENT_OPEN_MARKER: str = "/*"
|
|
44
|
-
BLOCK_COMMENT_CLOSE_MARKER: str = "*/"
|
|
45
|
-
|
|
46
43
|
INLINE_CODE_PATTERN: Pattern[str] = compile(r"``[^`]+``|`[^`]+`")
|
|
47
44
|
DOUBLE_QUOTED_SPAN_PATTERN: Pattern[str] = compile(r'"[^"\n]*"')
|
|
48
45
|
TRIPLE_QUOTED_BLOCK_PATTERN: Pattern[str] = compile(
|
|
@@ -10,13 +10,15 @@ if str(_HOOKS_ROOT) not in sys.path:
|
|
|
10
10
|
sys.path.insert(0, str(_HOOKS_ROOT))
|
|
11
11
|
|
|
12
12
|
from blocking.state_description_blocker import _extract_comment_lines
|
|
13
|
+
from hooks_constants.code_rules_enforcer_constants import (
|
|
14
|
+
JAVASCRIPT_BLOCK_COMMENT_CLOSER,
|
|
15
|
+
JAVASCRIPT_BLOCK_COMMENT_OPENER,
|
|
16
|
+
)
|
|
13
17
|
from hooks_constants.state_description_blocker_constants import (
|
|
14
18
|
ALL_BLOCK_COMMENT_EXTENSIONS,
|
|
15
19
|
ALL_COMMENT_TRANSITION_PATTERNS,
|
|
16
20
|
ALL_HASH_ONLY_EXTENSIONS,
|
|
17
21
|
ALL_MARKDOWN_EXTENSIONS,
|
|
18
|
-
BLOCK_COMMENT_CLOSE_MARKER,
|
|
19
|
-
BLOCK_COMMENT_OPEN_MARKER,
|
|
20
22
|
DOUBLE_QUOTE_BODY_GROUP,
|
|
21
23
|
SINGLE_QUOTE_BODY_GROUP,
|
|
22
24
|
TRIPLE_QUOTED_BLOCK_PATTERN,
|
|
@@ -47,19 +49,19 @@ def test_transition_patterns_do_not_match_unrelated_prose() -> None:
|
|
|
47
49
|
|
|
48
50
|
|
|
49
51
|
def test_block_comment_markers_close_a_single_line_block_comment() -> None:
|
|
50
|
-
text = f"{
|
|
52
|
+
text = f"{JAVASCRIPT_BLOCK_COMMENT_OPENER} note {JAVASCRIPT_BLOCK_COMMENT_CLOSER}\ncode();"
|
|
51
53
|
all_comment_lines = _extract_comment_lines(text, ".js")
|
|
52
54
|
assert all_comment_lines == [
|
|
53
|
-
(1, f"{
|
|
55
|
+
(1, f"{JAVASCRIPT_BLOCK_COMMENT_OPENER} note {JAVASCRIPT_BLOCK_COMMENT_CLOSER}")
|
|
54
56
|
]
|
|
55
57
|
|
|
56
58
|
|
|
57
59
|
def test_block_comment_markers_carry_an_unclosed_comment_to_the_next_line() -> None:
|
|
58
|
-
text = f"{
|
|
60
|
+
text = f"{JAVASCRIPT_BLOCK_COMMENT_OPENER} still open\nclosed here {JAVASCRIPT_BLOCK_COMMENT_CLOSER}"
|
|
59
61
|
all_comment_lines = _extract_comment_lines(text, ".js")
|
|
60
62
|
assert all_comment_lines == [
|
|
61
|
-
(1, f"{
|
|
62
|
-
(2, f"closed here {
|
|
63
|
+
(1, f"{JAVASCRIPT_BLOCK_COMMENT_OPENER} still open"),
|
|
64
|
+
(2, f"closed here {JAVASCRIPT_BLOCK_COMMENT_CLOSER}"),
|
|
63
65
|
]
|
|
64
66
|
|
|
65
67
|
|
|
@@ -48,27 +48,21 @@ TEST_PATH_PATTERNS: frozenset[str] = frozenset(
|
|
|
48
48
|
HOOK_INFRASTRUCTURE_PATTERNS: frozenset[str] = frozenset(
|
|
49
49
|
{
|
|
50
50
|
"/.claude/hooks/",
|
|
51
|
-
"\\.claude\\hooks\\",
|
|
52
|
-
"\\.claude/hooks/",
|
|
53
51
|
}
|
|
54
52
|
)
|
|
55
53
|
|
|
56
54
|
WORKFLOW_REGISTRY_PATTERNS: frozenset[str] = frozenset(
|
|
57
55
|
{
|
|
58
56
|
"/workflow/",
|
|
59
|
-
"\\workflow\\",
|
|
60
57
|
"_tab.py",
|
|
61
58
|
"/states.py",
|
|
62
|
-
"\\states.py",
|
|
63
59
|
"/modules.py",
|
|
64
|
-
"\\modules.py",
|
|
65
60
|
}
|
|
66
61
|
)
|
|
67
62
|
|
|
68
63
|
MIGRATION_PATH_PATTERNS: frozenset[str] = frozenset(
|
|
69
64
|
{
|
|
70
65
|
"/migrations/",
|
|
71
|
-
"\\migrations\\",
|
|
72
66
|
}
|
|
73
67
|
)
|
|
74
68
|
|
|
@@ -80,23 +74,14 @@ def is_test_file(file_path: str) -> bool:
|
|
|
80
74
|
|
|
81
75
|
def is_hook_infrastructure(file_path: str) -> bool:
|
|
82
76
|
path_normalized = file_path.lower().replace("\\", "/")
|
|
83
|
-
return any(
|
|
84
|
-
pattern.replace("\\", "/") in path_normalized
|
|
85
|
-
for pattern in HOOK_INFRASTRUCTURE_PATTERNS
|
|
86
|
-
)
|
|
77
|
+
return any(pattern in path_normalized for pattern in HOOK_INFRASTRUCTURE_PATTERNS)
|
|
87
78
|
|
|
88
79
|
|
|
89
80
|
def is_workflow_registry_file(file_path: str) -> bool:
|
|
90
81
|
path_normalized = file_path.lower().replace("\\", "/")
|
|
91
|
-
return any(
|
|
92
|
-
pattern.replace("\\", "/") in path_normalized
|
|
93
|
-
for pattern in WORKFLOW_REGISTRY_PATTERNS
|
|
94
|
-
)
|
|
82
|
+
return any(pattern in path_normalized for pattern in WORKFLOW_REGISTRY_PATTERNS)
|
|
95
83
|
|
|
96
84
|
|
|
97
85
|
def is_migration_file(file_path: str) -> bool:
|
|
98
86
|
path_normalized = file_path.lower().replace("\\", "/")
|
|
99
|
-
return any(
|
|
100
|
-
pattern.replace("\\", "/") in path_normalized
|
|
101
|
-
for pattern in MIGRATION_PATH_PATTERNS
|
|
102
|
-
)
|
|
87
|
+
return any(pattern in path_normalized for pattern in MIGRATION_PATH_PATTERNS)
|
|
@@ -14,6 +14,9 @@ _BLOCKING_DIR = str(Path(__file__).resolve().parent.parent / "blocking")
|
|
|
14
14
|
if _BLOCKING_DIR not in sys.path:
|
|
15
15
|
sys.path.insert(0, _BLOCKING_DIR)
|
|
16
16
|
|
|
17
|
+
import pytest # noqa: E402
|
|
18
|
+
|
|
19
|
+
from validators import exempt_paths # noqa: E402
|
|
17
20
|
from validators.exempt_paths import is_config_file # noqa: E402
|
|
18
21
|
from code_rules_path_utils import is_config_file as path_utils_is_config_file # noqa: E402
|
|
19
22
|
|
|
@@ -63,3 +66,20 @@ def test_is_config_file_is_identical_function_object_from_path_utils() -> None:
|
|
|
63
66
|
assert is_config_file is path_utils_is_config_file, (
|
|
64
67
|
"exempt_paths.is_config_file must be imported from code_rules_path_utils, not re-defined"
|
|
65
68
|
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@pytest.mark.parametrize(
|
|
72
|
+
"normalized_pattern_set_name",
|
|
73
|
+
[
|
|
74
|
+
"HOOK_INFRASTRUCTURE_PATTERNS",
|
|
75
|
+
"WORKFLOW_REGISTRY_PATTERNS",
|
|
76
|
+
"MIGRATION_PATH_PATTERNS",
|
|
77
|
+
],
|
|
78
|
+
)
|
|
79
|
+
def test_normalized_pattern_set_carries_no_backslash_entry(
|
|
80
|
+
normalized_pattern_set_name: str,
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Each set's consumer normalizes the path with .replace("\\\\", "/") but
|
|
83
|
+
not the pattern, so a backslash entry can never match."""
|
|
84
|
+
pattern_set = getattr(exempt_paths, normalized_pattern_set_name)
|
|
85
|
+
assert all("\\" not in each_pattern for each_pattern in pattern_set)
|
|
@@ -150,6 +150,18 @@ class TestPrintInCliEntryPoint:
|
|
|
150
150
|
tree = ast.parse(CLI_ENTRY_POINT_WITH_PRINTS)
|
|
151
151
|
assert check_print_in_production(tree, "packages/foo/scripts/run_job.py") == []
|
|
152
152
|
|
|
153
|
+
def test_diagnostics_path_allows_print(self) -> None:
|
|
154
|
+
tree = ast.parse(CLI_ENTRY_POINT_WITH_PRINTS)
|
|
155
|
+
assert check_print_in_production(tree, "theme_exports/tools/diagnostics/probe.py") == []
|
|
156
|
+
|
|
157
|
+
def test_windows_diagnostics_path_allows_print(self) -> None:
|
|
158
|
+
tree = ast.parse(CLI_ENTRY_POINT_WITH_PRINTS)
|
|
159
|
+
assert check_print_in_production(tree, "theme_exports\\tools\\diagnostics\\probe.py") == []
|
|
160
|
+
|
|
161
|
+
def test_near_miss_diagnostics_name_still_flagged(self) -> None:
|
|
162
|
+
tree = ast.parse(NEAR_MISS_WITH_PRINT)
|
|
163
|
+
assert len(check_print_in_production(tree, "run_diagnostics.py")) == 1
|
|
164
|
+
|
|
153
165
|
def test_near_miss_name_still_flagged(self) -> None:
|
|
154
166
|
tree = ast.parse(NEAR_MISS_WITH_PRINT)
|
|
155
167
|
assert len(check_print_in_production(tree, "publicli.py")) == 1
|
|
@@ -180,6 +192,8 @@ class TestCrossSurfaceConsistency:
|
|
|
180
192
|
"stp_preview/renderer.py",
|
|
181
193
|
"publicli.py",
|
|
182
194
|
"packages/foo/scripts/run_job.py",
|
|
195
|
+
"theme_exports/tools/diagnostics/probe.py",
|
|
196
|
+
"run_diagnostics.py",
|
|
183
197
|
):
|
|
184
198
|
production_allows = check_print_in_production(tree, path) == []
|
|
185
199
|
library_allows = check_library_print(source, path) == []
|
package/package.json
CHANGED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# The Agent Merges Its Own Green Pull Request
|
|
2
|
+
|
|
3
|
+
**When this applies:** Any pull request an agent opened or was asked to drive, once its checks report.
|
|
4
|
+
|
|
5
|
+
## Rule
|
|
6
|
+
|
|
7
|
+
The agent that drives a pull request merges it. A pull request that is green, carries no open review thread, and sits at a merge state of `clean` is merged in the same run that brought it there. Waiting for the owner to type "merge" parks finished work on the person the work was done for.
|
|
8
|
+
|
|
9
|
+
Three things stay with the owner, and nothing else does:
|
|
10
|
+
|
|
11
|
+
- A pull request the owner asked to hold.
|
|
12
|
+
- A change the owner said they want to read first.
|
|
13
|
+
- A repository whose branch rule requires an approving review the agent cannot give.
|
|
14
|
+
|
|
15
|
+
Where the branch rule requires zero approvals and one status check, that check is the gate, and the agent merges on its verdict.
|
|
16
|
+
|
|
17
|
+
Validation is the precondition, and green means every check reported on the exact head commit. A branch rule that requires one status check names the floor a merge needs; a pull request whose other checks are red or still running is held until they report.
|
|
18
|
+
|
|
19
|
+
## The precondition is mechanical
|
|
20
|
+
|
|
21
|
+
One command prints the verdict:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
python packages/claude-dev-env/scripts/agent_merge_check.py <owner>/<name> <number>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
It prints `MERGE` and exits 0 when the pull request is ready. It prints `HOLD` with the reason and exits 1 for a draft, for a head behind or conflicting with the base, for a required check that is not passing, for a check still running or red, and for an open review thread. It exits 2 when the state could not be read.
|
|
28
|
+
|
|
29
|
+
Each hold reason names its own repair, and each repair belongs to the agent:
|
|
30
|
+
|
|
31
|
+
| Hold | What the agent does |
|
|
32
|
+
|---|---|
|
|
33
|
+
| Head behind the base | Merge the base branch in and push |
|
|
34
|
+
| Head conflicts with the base | Merge the base branch in, resolve, push |
|
|
35
|
+
| A required check is red | Read the failing check, fix it, push |
|
|
36
|
+
| A check is red or still running | Fix it, or wait for it, then read the verdict again |
|
|
37
|
+
| A review thread is open | Answer it, push the fix, resolve the thread |
|
|
38
|
+
| The pull request is a draft | Mark it ready once the checks pass |
|
|
39
|
+
|
|
40
|
+
## When a gate elsewhere holds the merge command
|
|
41
|
+
|
|
42
|
+
A session working inside another repository can sit behind that repository's own pre-merge gate, which reads the checkout the session works in and refuses a merge command whatever repository the pull request belongs to. That session hands the merge to the session that owns this repository's pull requests, by message, naming the pull request. The receiving session reads the verdict above and merges. The hand-off carries the work; it never lands on the owner.
|
|
43
|
+
|
|
44
|
+
## After the merge
|
|
45
|
+
|
|
46
|
+
Delete nothing by hand. The repository deletes the head branch on merge.
|
|
47
|
+
|
|
48
|
+
## Sibling rules
|
|
49
|
+
|
|
50
|
+
| Rule | Role |
|
|
51
|
+
|---|---|
|
|
52
|
+
| [`git-workflow.md`](git-workflow.md) | Draft first, and confirm each required context fired after the push |
|
|
53
|
+
| [`ci-owns-the-gate.md`](ci-owns-the-gate.md) | The gate runs once, and it runs on CI |
|
|
54
|
+
| [`correction-lens.md`](correction-lens.md) | A correction becomes a control at the highest layer that can hold it |
|
|
@@ -43,6 +43,16 @@ Record a row in the archive manifest for each file: why it went, the `git mv`
|
|
|
43
43
|
that restores it, and every other edit a restore has to undo. A restore that has
|
|
44
44
|
to be reconstructed from the diff is not recoverable in any useful sense.
|
|
45
45
|
|
|
46
|
+
## Exempt files
|
|
47
|
+
|
|
48
|
+
The "Never archived" section of `rules-archived/ARCHIVE-MANIFEST.md` names the
|
|
49
|
+
files this procedure leaves alone. Read that section before a prune moves
|
|
50
|
+
anything. A pass that reaches an exempt file stops there and reports which file
|
|
51
|
+
it was, so the prune's other moves still land.
|
|
52
|
+
|
|
53
|
+
`rules/correction-lens.md` is on that list today. Editing an exempt file to
|
|
54
|
+
sharpen it is ordinary work.
|
|
55
|
+
|
|
46
56
|
## Make the archive stick
|
|
47
57
|
|
|
48
58
|
Two things do not follow the file on their own.
|
package/rules/code-standards.md
CHANGED
|
@@ -27,6 +27,7 @@ BDD is the outer process and TDD is the inner loop: [`bdd.md`](bdd.md) discovers
|
|
|
27
27
|
|
|
28
28
|
| Concern | Rule file |
|
|
29
29
|
|---|---|
|
|
30
|
+
| Handling a correction from the user | [`correction-lens.md`](correction-lens.md) |
|
|
30
31
|
| Task tracking / worker completion | [`workers-done-before-complete.md`](workers-done-before-complete.md) |
|
|
31
32
|
| Multi-step task list | skill `task-build` (see agents catalog) |
|
|
32
33
|
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Correction Lens
|
|
2
|
+
|
|
3
|
+
**Standing rule. This file is never archived and never removed.** The permanence
|
|
4
|
+
clause at the end of this document states what that binds.
|
|
5
|
+
|
|
6
|
+
**When this applies:** Every correction the user gives. A "no, do it this way", a
|
|
7
|
+
"stop writing that word", a repeated review comment, a fix the user makes by hand
|
|
8
|
+
after an agent hands work back. Each one is evidence that a control is missing,
|
|
9
|
+
and the correction is handled by building that control.
|
|
10
|
+
|
|
11
|
+
## Rule
|
|
12
|
+
|
|
13
|
+
A correction runs through five layers, in this order, and the lesson lands at the
|
|
14
|
+
highest layer that can hold it. The change opens in the same run as the
|
|
15
|
+
correction.
|
|
16
|
+
|
|
17
|
+
| Priority | Layer | What holds the lesson | The lesson lands as |
|
|
18
|
+
|---|---|---|---|
|
|
19
|
+
| 1 | Codebase | The mistake is impossible by how the code is written | A type, a signature, a data structure, an API shape, a deleted branch |
|
|
20
|
+
| 2 | Static analysis | A program reads the tree and decides | A lint rule, an enforcer check, a paired test, a CI gate |
|
|
21
|
+
| 3 | Review tooling | A reviewer or a review bot reads the criterion | A `CODE_RULES.md` row, a `.cursor/BUGBOT.md` pointer, a Graphite or BugBot rule |
|
|
22
|
+
| 4 | Skill | An agent follows a procedure | A skill under the agents home |
|
|
23
|
+
| 5 | Style guide | Word choice and prose shape | A row in a `rules/*.md` file or a style document |
|
|
24
|
+
|
|
25
|
+
Layer 1 is the goal every time. A lesson encoded there needs no reader, no run,
|
|
26
|
+
and no agent to remember it. The wrong call has nowhere to live.
|
|
27
|
+
|
|
28
|
+
## Choosing the layer
|
|
29
|
+
|
|
30
|
+
Three sentences close every correction:
|
|
31
|
+
|
|
32
|
+
1. The layer chosen.
|
|
33
|
+
2. Why each higher layer cannot hold this lesson.
|
|
34
|
+
3. The change opened at the chosen layer, by pull request.
|
|
35
|
+
|
|
36
|
+
A layer holds a lesson when its own test passes:
|
|
37
|
+
|
|
38
|
+
- **Layer 1 holds it** when a type, a signature, or a data structure can make the
|
|
39
|
+
wrong call fail to compile, fail to construct, or fail to exist.
|
|
40
|
+
- **Layer 2 holds it** when a program reading the tree can separate right from
|
|
41
|
+
wrong with no judgment call.
|
|
42
|
+
- **Layer 3 holds it** when a reader needs the criterion in front of them and a
|
|
43
|
+
program cannot decide it.
|
|
44
|
+
- **Layer 4 holds it** when the lesson is a procedure with steps an agent runs.
|
|
45
|
+
- **Layer 5 holds it** when the lesson is word choice or the shape of prose.
|
|
46
|
+
|
|
47
|
+
Effort rules out no layer. A layer is ruled out when it lacks the capability to
|
|
48
|
+
hold the lesson, and the sentence that rules it out says which capability is
|
|
49
|
+
missing. "A lint for this would take a day" leaves layer 2 in play.
|
|
50
|
+
|
|
51
|
+
Two layers often hold one correction. Take both: the shape at layer 1 and the
|
|
52
|
+
check at layer 2 cost one run together and each covers what the other misses.
|
|
53
|
+
|
|
54
|
+
## A repeated correction moves up a layer
|
|
55
|
+
|
|
56
|
+
The same correction arriving a second time is the measurement. The layer chosen
|
|
57
|
+
the first time was too low. Move the lesson one layer up and say so in the same
|
|
58
|
+
run.
|
|
59
|
+
|
|
60
|
+
A review comment that repeats across pull requests reads the same way. Each
|
|
61
|
+
repetition names the layer below the reviewer as the one that needs the control.
|
|
62
|
+
Count the repetitions.
|
|
63
|
+
|
|
64
|
+
## Where the control lands
|
|
65
|
+
|
|
66
|
+
A control lands in this package, so every session that loads this environment
|
|
67
|
+
carries it, on any machine, under any model. A control written into one
|
|
68
|
+
project's instructions reaches that project alone, and a control written into a
|
|
69
|
+
chat reply reaches that conversation alone.
|
|
70
|
+
|
|
71
|
+
Memory holds the decision. The control holds the behavior. A correction that
|
|
72
|
+
produced a memory file and nothing else has been recorded and never encoded, so
|
|
73
|
+
the rule still binds. Open the control.
|
|
74
|
+
|
|
75
|
+
## Trust grows as the controls catch the common mistakes
|
|
76
|
+
|
|
77
|
+
Add agents after the controls catch what agents get wrong, and start from a
|
|
78
|
+
workflow small enough to watch. Each repeated correction becomes a stronger
|
|
79
|
+
control, and the number of agents rises behind it.
|
|
80
|
+
[`docs/high-trust-agent-delivery.md`](../docs/high-trust-agent-delivery.md)
|
|
81
|
+
carries the model this comes from, including the layered-controls picture and
|
|
82
|
+
the inner and outer delivery loops.
|
|
83
|
+
|
|
84
|
+
## Permanence
|
|
85
|
+
|
|
86
|
+
This file stays in `rules/`. A prune pass, an archive sweep, a consolidation, or
|
|
87
|
+
a rewrite that would move it into `rules-archived/` stops at this line. The
|
|
88
|
+
"Never archived" section of
|
|
89
|
+
[`packages/claude-dev-env/rules-archived/ARCHIVE-MANIFEST.md`](../rules-archived/ARCHIVE-MANIFEST.md)
|
|
90
|
+
names it, and
|
|
91
|
+
[`archiving-agent-config.md`](archiving-agent-config.md) names the same
|
|
92
|
+
exemption from the archiving procedure's side.
|
|
93
|
+
|
|
94
|
+
An edit that sharpens this rule is welcome. The file stays.
|
|
95
|
+
|
|
96
|
+
## Excerpt for repository-instruction sessions
|
|
97
|
+
|
|
98
|
+
Codex reads its repository `AGENTS.md`; this excerpt supplies the standalone
|
|
99
|
+
contract.
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
Correction handling for this run, from rules/correction-lens.md.
|
|
103
|
+
|
|
104
|
+
Every correction the user gives becomes a control. Run it through five layers
|
|
105
|
+
and land it at the highest one that can hold it:
|
|
106
|
+
|
|
107
|
+
1. Codebase. The mistake is impossible by how the code is written: a type, a
|
|
108
|
+
signature, a data structure, an API shape.
|
|
109
|
+
2. Static analysis. A program reads the tree and decides: a lint rule, an
|
|
110
|
+
enforcer check, a paired test, a CI gate.
|
|
111
|
+
3. Review tooling. A reviewer or a review bot reads the criterion: a code-rules
|
|
112
|
+
row, a BUGBOT pointer, a Graphite rule.
|
|
113
|
+
4. Skill. An agent follows a procedure.
|
|
114
|
+
5. Style guide. Word choice and prose shape.
|
|
115
|
+
|
|
116
|
+
Close the correction with three sentences: the layer chosen, why each higher
|
|
117
|
+
layer cannot hold the lesson, and the change opened at that layer in this run.
|
|
118
|
+
Effort rules out no layer; a missing capability does, and you name which one.
|
|
119
|
+
|
|
120
|
+
The same correction arriving twice means the layer was too low. Move it up one
|
|
121
|
+
layer and say so.
|
|
122
|
+
|
|
123
|
+
Land the control in the shared environment package, so every session carries
|
|
124
|
+
it. A memory file records the decision and encodes nothing.
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Sibling rules
|
|
128
|
+
|
|
129
|
+
| Rule | Role |
|
|
130
|
+
|---|---|
|
|
131
|
+
| [`flag-non-breaking-findings.md`](flag-non-breaking-findings.md) | A gate blocks on a breaking finding and records a smell |
|
|
132
|
+
| [`code-standards.md`](code-standards.md) | The layer map of contract, pointer, enforcer, lint, session rules |
|
|
133
|
+
| [`archiving-agent-config.md`](archiving-agent-config.md) | How a rule leaves service, and which files are exempt |
|
|
134
|
+
| [`falsify-before-green.md`](falsify-before-green.md) | A new layer-2 check counts once it has run red on a named break |
|
|
@@ -97,6 +97,17 @@ loading, so the gate fails. A non-canonical filename and a Git mode other than
|
|
|
97
97
|
`SEVERITY_BY_RULE_ID` in that module is the whole declaration. Add a check,
|
|
98
98
|
add its row.
|
|
99
99
|
|
|
100
|
+
`scripts/repository_policy.py` splits the committed-tree checks the same way.
|
|
101
|
+
`SEVERITY_BY_CHECK_ID` in `repository_checks/config/constants.py` carries one
|
|
102
|
+
row per check id, and a check id with no row reads as breaking. The
|
|
103
|
+
`package-inventory` check is the one smell there. A production file whose
|
|
104
|
+
package inventory omits its row still imports and still runs, so the check
|
|
105
|
+
prints its finding with an `advisory:` prefix, records it in the ledger, and
|
|
106
|
+
leaves the tree passing. Every other committed-tree check blocks: a `CLAUDE.md`
|
|
107
|
+
naming a file that does not exist, an env-var row naming a file that never
|
|
108
|
+
reads the variable, a test outside the testpaths allowlist, and a tracked
|
|
109
|
+
secret.
|
|
110
|
+
|
|
100
111
|
## Sibling rules
|
|
101
112
|
|
|
102
113
|
| Rule | Role |
|
|
@@ -18,6 +18,17 @@ A pause is a request you deliver, not a state you enter. Put it in the channel t
|
|
|
18
18
|
|
|
19
19
|
Before you end any turn, read your last paragraph. If it is a plan, an analysis, a list of next steps, or a statement of intent ("I'll run the tests", "next I'll wire it up"), do that work with tool calls before you stop. End the turn only when the task is done or you are blocked on input only the user can give.
|
|
20
20
|
|
|
21
|
+
Four turn endings leave owed work unstarted. Each one is a stop to remove:
|
|
22
|
+
|
|
23
|
+
- A summary of the work so far that closes by naming the next step, with no tool call to start it.
|
|
24
|
+
- An offer to continue unless the user objects.
|
|
25
|
+
- A list of decisions for the user when none of them blocks the remaining work.
|
|
26
|
+
- A pause chosen because the turn ran long or a milestone finished.
|
|
27
|
+
|
|
28
|
+
A status note or a recommendation on an open decision rides in the same message as the next tool call. The stops that stay are the two where nothing can move: the next step needs the user, or the blocker is a control that is protected from you on purpose. Confirmation before a risky or destructive action still applies.
|
|
29
|
+
|
|
30
|
+
A harness or routine that drives an unattended run reads a turn that ends in text as a report. When open checklist items remain and the turn names no blocker, it sends one short message naming those items. It stops after two or three such continuations on one task, so a stuck run ends where a reader can review it. [Prompting Claude Opus 5.5](https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/prompting-claude-opus-5-5#unattended-agentic-runs) is the source for this list and this loop.
|
|
31
|
+
|
|
21
32
|
In an autonomous pipeline the user cannot answer mid-task. For reversible actions that follow from the original request, act without asking; save any follow-up offers for after the task is done.
|
|
22
33
|
|
|
23
34
|
Authority the task already granted stays granted. A later preference about tone, brevity, or reply format changes how you report, never what you are allowed to do. Re-asking for permission the task already gave hands the work back to the user, who then does it by hand.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# No Contrast Framing
|
|
2
|
+
|
|
3
|
+
**When this applies:** Every sentence a person reads. A chat reply, a commit
|
|
4
|
+
message, a pull request title or body, a review comment, an issue, a rule file,
|
|
5
|
+
a document, a plan, a memory file.
|
|
6
|
+
|
|
7
|
+
## Rule
|
|
8
|
+
|
|
9
|
+
State what is true. Leave the rejected reading out.
|
|
10
|
+
|
|
11
|
+
A contrast defines its subject against something the writer already decided
|
|
12
|
+
against, so the reader carries two readings where one would do, and the second
|
|
13
|
+
one is the writer's own discarded draft. The sentence says more and shows less.
|
|
14
|
+
|
|
15
|
+
Six forms carry the whole ban. Each row names the form the check reports.
|
|
16
|
+
|
|
17
|
+
| Form | Shape | Write this |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `trailing-comma-not` | `a diff regression, not shared infrastructure` | `a diff regression` |
|
|
20
|
+
| `corrective-it-is-not` | `it is not a flake, it is a bug` | `a bug`, then the evidence that shows it |
|
|
21
|
+
| `substitution-rather-than` | `park it rather than fix it` | `park it` |
|
|
22
|
+
| `additive-not-just` | `not just for today` | the point the sentence was building toward |
|
|
23
|
+
| `comparative-ranking` | `being precise matters more than trying to cover them` | `be precise about these three` |
|
|
24
|
+
| `substitution-as-opposed-to` | `a warning as opposed to a failure` | `a warning` |
|
|
25
|
+
|
|
26
|
+
A comparison of quantities stays. "The function runs more than 30 lines" counts
|
|
27
|
+
lines. The ban covers the comparison that ranks one course of action over
|
|
28
|
+
another the writer is turning down.
|
|
29
|
+
|
|
30
|
+
Three shapes stay quiet by design. A backticked span or a fenced block carries
|
|
31
|
+
an example, so the check blanks it first. A line opening with `>` quotes
|
|
32
|
+
someone else, whose words are theirs to write. A `, not` clause reports only
|
|
33
|
+
when no bare `not` already sits earlier in the line, so a list of clauses
|
|
34
|
+
reports its first `, not` and stays quiet on the rest, as in
|
|
35
|
+
`a diff regression, not shared code, not a workaround`. A list whose own
|
|
36
|
+
opening word is a bare `not`, as in "not in chat, not in a commit message, not
|
|
37
|
+
in a body", keeps even its first `, not` quiet. A release automation body
|
|
38
|
+
passes the post linter untouched, since the bot builds it from commit
|
|
39
|
+
subjects and reads it back as machine input.
|
|
40
|
+
|
|
41
|
+
When the sentence thins after the contrast comes out, the missing piece is
|
|
42
|
+
evidence. Name it: the failing check, the log line, the measured number, the
|
|
43
|
+
file and the line.
|
|
44
|
+
|
|
45
|
+
## Where it is enforced
|
|
46
|
+
|
|
47
|
+
| Surface | What runs |
|
|
48
|
+
|---|---|
|
|
49
|
+
| Authored Markdown in the repository | The staged policy lint's `contrast-framing` rule, on every changed file, graded against the file's prior text |
|
|
50
|
+
| A pull request or issue title and body, and a comment | `scripts/durable_post_lint.py`, which every post passes through before it reaches GitHub |
|
|
51
|
+
| A chat reply | The writer, reading the sentence back before sending |
|
|
52
|
+
|
|
53
|
+
The pattern list lives in
|
|
54
|
+
`scripts/dev_env_scripts_constants/contrast_framing_constants.py`, and both
|
|
55
|
+
lints read that one list. A synchronization test requires a row in the table
|
|
56
|
+
above for every form the list carries.
|
|
57
|
+
|
|
58
|
+
A chat reply reaches no check, so the same list is what the writer reads the
|
|
59
|
+
sentence against: a comma followed by `not`, a `rather than`, a `not just`, a
|
|
60
|
+
ranking of one thing over another.
|
|
61
|
+
|
|
62
|
+
## Sibling rules
|
|
63
|
+
|
|
64
|
+
| Rule | Role |
|
|
65
|
+
|---|---|
|
|
66
|
+
| [`asd-ste100-language.md`](asd-ste100-language.md) | Plain word choice, sentence style, and tone |
|
|
67
|
+
| [`correction-lens.md`](correction-lens.md) | Every correction becomes a control at the highest layer that can hold it |
|
|
68
|
+
| [`research-mode.md`](research-mode.md) | A claim carries its source |
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Review Closure Is a Check
|
|
2
|
+
|
|
3
|
+
**When this applies:** Any pull request an agent drives, from the first review comment on it to the merge.
|
|
4
|
+
|
|
5
|
+
## Rule
|
|
6
|
+
|
|
7
|
+
A review finding on the head is answered before the pull request merges. The agent driving it replies, pushes the fix, or both. The check named `Review closure` reads that state on every push and on every review event, and reports red while a finding waits.
|
|
8
|
+
|
|
9
|
+
One command prints the same verdict:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
python packages/claude-dev-env/scripts/review_closure.py <owner>/<name> <number>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
It prints `CLOSED` and exits 0 when every finding on the head is answered. It prints `OPEN`, one line per waiting finding, and exits 1. It exits 2 when the state could not be read.
|
|
16
|
+
|
|
17
|
+
## What closes a finding
|
|
18
|
+
|
|
19
|
+
| The thread | Closed by |
|
|
20
|
+
|---|---|
|
|
21
|
+
| A review comment on code | A push that replaced the code it points at |
|
|
22
|
+
| A review comment on code | A reply from the account driving the pull request |
|
|
23
|
+
| A review comment on code | Resolution, where the comment carries no red circle |
|
|
24
|
+
| A red-circle finding | A reply from the driving account, or a push that replaced the code |
|
|
25
|
+
| A thread the driving account opened | Itself |
|
|
26
|
+
| A blocking `Claude Approvals` row | A push, which moves the head the check reports on |
|
|
27
|
+
|
|
28
|
+
A red circle marks a finding a review states as blocking, so resolution in silence leaves it open. The reply says what changed or why the finding stands, and the reviewer reads it beside the diff.
|
|
29
|
+
|
|
30
|
+
The driving account is the one that opened the pull request. Where the agent comments under a second login, `--driver-login <login>` names it, repeatably.
|
|
31
|
+
|
|
32
|
+
## Where the check runs
|
|
33
|
+
|
|
34
|
+
`.github/workflows/review-closure.yml` runs it here on a push to a pull request, on a submitted or dismissed review, and on a review comment. Each run reports on the pull request's head commit, so a finding posted after the last push still turns the check red.
|
|
35
|
+
|
|
36
|
+
`Echo-Visuals-Inc/python-automation` runs the same command from its own workflow, against the revision of this package that its workflow pins.
|
|
37
|
+
|
|
38
|
+
## Sibling rules
|
|
39
|
+
|
|
40
|
+
| Rule | Role |
|
|
41
|
+
|---|---|
|
|
42
|
+
| [`agent-merges-its-own-green-pull-request.md`](agent-merges-its-own-green-pull-request.md) | The agent that drives a pull request merges it once its gate passes |
|
|
43
|
+
| [`git-workflow.md`](git-workflow.md) | Draft first, and confirm each required context fired after the push |
|
|
44
|
+
| [`correction-lens.md`](correction-lens.md) | A correction becomes a control at the highest layer that can hold it |
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env pwsh
|
|
2
|
+
<#
|
|
3
|
+
.SYNOPSIS
|
|
4
|
+
Run every Pester suite under a tests directory and exit on its verdict.
|
|
5
|
+
|
|
6
|
+
.DESCRIPTION
|
|
7
|
+
check.ps1 calls this script as a child process so Invoke-Tool reads a
|
|
8
|
+
process exit code the way it does for ruff, mypy and pytest. Pester is a
|
|
9
|
+
cmdlet and leaves $LASTEXITCODE alone, so running it in-process would
|
|
10
|
+
report every suite as passing.
|
|
11
|
+
|
|
12
|
+
A missing Pester module exits non-zero. A gate that cannot run its check
|
|
13
|
+
has not passed it, and a skip here would leave the suites reporting
|
|
14
|
+
nothing while the tree reads as covered.
|
|
15
|
+
|
|
16
|
+
.PARAMETER TestsRoot
|
|
17
|
+
Directory holding the *.Tests.ps1 suites.
|
|
18
|
+
|
|
19
|
+
.OUTPUTS
|
|
20
|
+
Pester's own detailed output. Exit code 0 when every suite passes.
|
|
21
|
+
#>
|
|
22
|
+
[CmdletBinding()]
|
|
23
|
+
param(
|
|
24
|
+
[Parameter(Mandatory = $true)]
|
|
25
|
+
[string]$TestsRoot
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
$ErrorActionPreference = 'Stop'
|
|
29
|
+
|
|
30
|
+
if (-not (Get-Module -ListAvailable -Name Pester)) {
|
|
31
|
+
Write-Error "Pester is not installed, so the suites under $TestsRoot cannot run."
|
|
32
|
+
exit 1
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
Import-Module Pester -MinimumVersion 5.0
|
|
36
|
+
|
|
37
|
+
$suiteConfiguration = New-PesterConfiguration
|
|
38
|
+
$suiteConfiguration.Run.Path = $TestsRoot
|
|
39
|
+
$suiteConfiguration.Run.Exit = $true
|
|
40
|
+
$suiteConfiguration.Output.Verbosity = 'Detailed'
|
|
41
|
+
|
|
42
|
+
Invoke-Pester -Configuration $suiteConfiguration
|