claude-dev-env 7.5.0 → 7.6.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/everything-search/scripts/everything_search.py +11 -0
- package/.agents/skills/everything-search/scripts/test_everything_search.py +1 -0
- package/.agents/skills/fresh-branch/SKILL.md +1 -0
- package/.agents/skills/fresh-branch/scripts/fresh_branch_git_commands.py +3 -0
- package/.agents/skills/fresh-branch/scripts/subprocess_window_access.py +43 -0
- package/.agents/skills/syncing-submodules/SKILL.md +1 -0
- package/.agents/skills/syncing-submodules/scripts/submodule_sync.py +4 -0
- package/.agents/skills/syncing-submodules/scripts/subprocess_window_access.py +43 -0
- package/.agents/skills/test-runner/SKILL.md +1 -0
- package/.agents/skills/test-runner/scripts/preflight_checks.py +5 -0
- package/.agents/skills/test-runner/scripts/run_tests.py +11 -0
- package/.agents/skills/test-runner/scripts/subprocess_window_access.py +43 -0
- package/.agents/skills/test-runner/scripts/test_run_tests.py +9 -7
- package/_shared/pr-loop/scripts/check_bugbot_ci.py +3 -0
- package/_shared/pr-loop/scripts/check_convergence_gates.py +3 -0
- package/_shared/pr-loop/scripts/check_pending_reviews.py +3 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/added_line_maps.py +3 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/baseline_import_isolation.py +3 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +5 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/git_file_sets.py +4 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_regression.py +3 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_running.py +3 -0
- package/_shared/pr-loop/scripts/codex_usage_probe.py +3 -0
- package/_shared/pr-loop/scripts/copilot_quota.py +3 -0
- package/_shared/pr-loop/scripts/fetch_copilot_reviews.py +3 -0
- package/_shared/pr-loop/scripts/fix_hookspath.py +7 -0
- package/_shared/pr-loop/scripts/post_fix_reply.py +4 -0
- package/_shared/pr-loop/scripts/preflight.py +7 -0
- package/_shared/pr-loop/scripts/preflight_self_heal.py +5 -0
- package/_shared/pr-loop/scripts/run_codex_review.py +3 -0
- package/_shared/pr-loop/scripts/subprocess_window_access.py +41 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +5 -0
- package/_shared/process-tree/scripts/process_tree_kill.py +3 -0
- package/_shared/process-tree/scripts/subprocess_window_access.py +41 -0
- package/bin/install-constants.mjs +4 -2
- package/bin/install.cursor-rules.test.mjs +57 -15
- package/bin/install.mjs +10 -15
- package/bin/pstack-model-preferences.claude.json +23 -0
- package/hooks/advisory/pr_done_reminder.py +7 -0
- package/hooks/advisory/refactor_guard.py +8 -0
- package/hooks/advisory/test_pr_done_reminder.py +21 -0
- package/hooks/advisory/test_refactor_guard.py +40 -0
- package/hooks/blocking/block_main_commit.py +4 -0
- package/hooks/blocking/code_rules_comments.py +39 -5
- package/hooks/blocking/pii_prevention_blocker_parts/repository_exemption.py +3 -0
- package/hooks/blocking/precommit_code_rules_gate.py +3 -0
- package/hooks/blocking/tdd_enforcer_parts/content_analysis.py +19 -3
- package/hooks/blocking/tdd_enforcer_parts/git_tracking.py +4 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_content_analysis.py +25 -0
- package/hooks/blocking/test_code_rules_enforcer_comment_string_awareness.py +27 -0
- package/hooks/blocking/test_code_rules_enforcer_exempt_marker_chained.py +10 -0
- package/hooks/git-hooks/git_hooks_constants/verification_notice_constants.py +0 -2
- package/hooks/git-hooks/pull_request_handoff.py +3 -0
- package/hooks/git-hooks/subprocess_window_access.py +41 -0
- package/hooks/git-hooks/test_verification_start.py +4 -0
- package/hooks/git-hooks/verification_notice_context.py +3 -0
- package/hooks/git-hooks/verification_start.py +7 -8
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +2 -0
- package/hooks/hooks_constants/subprocess_window.py +141 -0
- package/hooks/hooks_constants/subprocess_window_constants.py +9 -0
- package/hooks/hooks_constants/test_code_rules_enforcer_constants.py +6 -0
- package/hooks/hooks_constants/test_subprocess_window.py +128 -0
- package/hooks/lifecycle/enter_worktree_origin_prefetch.py +3 -0
- package/hooks/validation/mypy_validator.py +4 -0
- package/hooks/validators/README.md +1 -0
- package/hooks/validators/git_checks.py +4 -0
- package/hooks/validators/health_check.py +3 -0
- package/hooks/validators/hook_timing_harness.py +3 -0
- package/hooks/validators/mypy_integration.py +4 -0
- package/hooks/validators/ruff_integration.py +6 -0
- package/hooks/validators/run_all_validators.py +7 -0
- package/hooks/validators/subprocess_window_access.py +41 -0
- package/hooks/workflow/auto_formatter.py +8 -0
- package/hooks/workflow/test_auto_formatter.py +39 -0
- package/package.json +1 -1
- package/rules/pstack-models.md +4 -3
- package/scripts/automatic_advisory/checkout.py +8 -1
- package/scripts/automatic_advisory/cli.py +9 -3
- package/scripts/automatic_advisory/config/constants.py +0 -2
- package/scripts/automatic_advisory/execution.py +7 -1
- package/scripts/automatic_advisory/git.py +9 -1
- package/scripts/automatic_advisory/process_host.py +3 -0
- package/scripts/claude_chain_runner.py +3 -0
- package/scripts/gh_artifact_upload.py +3 -0
- package/scripts/grok_headless_runner.py +3 -0
- package/scripts/grok_patch_artifacts.py +4 -0
- package/scripts/grok_worker_preflight.py +3 -0
- package/scripts/invoke_code_review.py +3 -0
- package/scripts/local_verification/command_runner.py +3 -0
- package/scripts/local_verification/git_state.py +3 -0
- package/scripts/policy_lint/adapter_detectors.py +48 -2
- package/scripts/policy_lint/adapter_pairing.py +150 -2
- package/scripts/policy_lint/adapters.py +46 -4
- package/scripts/policy_lint/config/approved_test_pairs.py +25 -9
- package/scripts/policy_lint/config/constants.py +3 -0
- package/scripts/policy_lint/registry.py +1 -1
- package/scripts/policy_lint/selection_git.py +11 -0
- package/scripts/repository_checks/config/constants.py +5 -0
- package/scripts/repository_checks/config/policy_document.py +19 -0
- package/scripts/repository_checks/email_exemptions.py +14 -71
- package/scripts/repository_checks/path_exemptions.py +35 -0
- package/scripts/repository_checks/policy_document.py +120 -0
- package/scripts/repository_checks/tracked_secrets.py +27 -4
- package/scripts/setup_project_paths.py +3 -0
- package/scripts/spawn_grok_batch.py +3 -0
- package/scripts/subprocess_window_access.py +41 -0
- package/scripts/test_dispatcher_profile_import.py +4 -0
- package/scripts/tests/test_automatic_advisory_window_flags.py +17 -11
- package/scripts/tests/test_email_exemptions.py +37 -0
- package/scripts/tests/test_path_exemptions.py +164 -0
- package/scripts/tests/test_policy_document.py +104 -0
- package/scripts/tests/test_policy_lint_rules_function_length_scope.py +101 -0
- package/scripts/tests/test_policy_lint_rules_pairing.py +375 -0
- package/scripts/tests/test_policy_lint_rules_registry.py +116 -0
- package/scripts/tests/test_policy_lint_selection.py +34 -0
- package/scripts/tests/test_subprocess_window_access.py +118 -0
- package/scripts/tests/test_tracked_secrets.py +79 -0
- package/scripts/validate_instruction_pairs.py +3 -0
- package/scripts/verify_installable_package.py +5 -0
- package/scripts/automatic_advisory/window_flags.py +0 -41
- package/scripts/repository_checks/config/email_exemptions.py +0 -10
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Tests for the shared hidden-console creation flags."""
|
|
2
|
+
|
|
3
|
+
import pathlib
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Any, Mapping
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
_HOOKS_ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
11
|
+
if str(_HOOKS_ROOT) not in sys.path:
|
|
12
|
+
sys.path.insert(0, str(_HOOKS_ROOT))
|
|
13
|
+
|
|
14
|
+
from hooks_constants.subprocess_window import (
|
|
15
|
+
detached_hidden_window_creation_flags,
|
|
16
|
+
hidden_window_creation_flags,
|
|
17
|
+
inherited_stream_startup_info,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
WINDOWS_NO_WINDOW_FLAG = 0x08000000
|
|
21
|
+
WINDOWS_NEW_PROCESS_GROUP_FLAG = 0x00000200
|
|
22
|
+
WINDOWS_ONLY = pytest.mark.skipif(
|
|
23
|
+
sys.platform != "win32", reason="subprocess.STARTUPINFO exists only on Windows"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_windows_reports_the_hidden_console_flag(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
28
|
+
monkeypatch.setattr(sys, "platform", "win32")
|
|
29
|
+
monkeypatch.setattr(subprocess, "CREATE_NO_WINDOW", WINDOWS_NO_WINDOW_FLAG, raising=False)
|
|
30
|
+
assert hidden_window_creation_flags() == WINDOWS_NO_WINDOW_FLAG
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_other_platforms_report_no_creation_flags(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
34
|
+
monkeypatch.setattr(sys, "platform", "linux")
|
|
35
|
+
assert hidden_window_creation_flags() == 0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_windows_without_the_flag_attribute_reports_no_creation_flags(
|
|
39
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
40
|
+
) -> None:
|
|
41
|
+
monkeypatch.setattr(sys, "platform", "win32")
|
|
42
|
+
monkeypatch.delattr(subprocess, "CREATE_NO_WINDOW", raising=False)
|
|
43
|
+
assert hidden_window_creation_flags() == 0
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_windows_detached_flags_add_a_new_process_group(
|
|
47
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
48
|
+
) -> None:
|
|
49
|
+
monkeypatch.setattr(sys, "platform", "win32")
|
|
50
|
+
monkeypatch.setattr(subprocess, "CREATE_NO_WINDOW", WINDOWS_NO_WINDOW_FLAG, raising=False)
|
|
51
|
+
monkeypatch.setattr(
|
|
52
|
+
subprocess,
|
|
53
|
+
"CREATE_NEW_PROCESS_GROUP",
|
|
54
|
+
WINDOWS_NEW_PROCESS_GROUP_FLAG,
|
|
55
|
+
raising=False,
|
|
56
|
+
)
|
|
57
|
+
assert detached_hidden_window_creation_flags() == (
|
|
58
|
+
WINDOWS_NO_WINDOW_FLAG | WINDOWS_NEW_PROCESS_GROUP_FLAG
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_other_platforms_report_no_detached_creation_flags(
|
|
63
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
64
|
+
) -> None:
|
|
65
|
+
monkeypatch.setattr(sys, "platform", "linux")
|
|
66
|
+
assert detached_hidden_window_creation_flags() == 0
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _captured_run_options(monkeypatch: pytest.MonkeyPatch) -> Mapping[str, Any]:
|
|
70
|
+
"""Run one command through a patched subprocess.run and return its options."""
|
|
71
|
+
all_captured_options: dict[str, Any] = {}
|
|
72
|
+
|
|
73
|
+
def fake_run(*_all_arguments: Any, **all_options: Any) -> None:
|
|
74
|
+
all_captured_options.update(all_options)
|
|
75
|
+
|
|
76
|
+
monkeypatch.setattr(subprocess, "run", fake_run)
|
|
77
|
+
subprocess.run(
|
|
78
|
+
["git", "status"],
|
|
79
|
+
creationflags=hidden_window_creation_flags(),
|
|
80
|
+
)
|
|
81
|
+
return all_captured_options
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_a_call_site_passes_the_hidden_flag_on_windows(
|
|
85
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
86
|
+
) -> None:
|
|
87
|
+
monkeypatch.setattr(sys, "platform", "win32")
|
|
88
|
+
monkeypatch.setattr(subprocess, "CREATE_NO_WINDOW", WINDOWS_NO_WINDOW_FLAG, raising=False)
|
|
89
|
+
assert _captured_run_options(monkeypatch)["creationflags"] == WINDOWS_NO_WINDOW_FLAG
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_a_call_site_passes_the_default_flags_off_windows(
|
|
93
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
94
|
+
) -> None:
|
|
95
|
+
monkeypatch.setattr(sys, "platform", "linux")
|
|
96
|
+
assert _captured_run_options(monkeypatch)["creationflags"] == 0
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@WINDOWS_ONLY
|
|
100
|
+
def test_windows_startup_info_asks_for_a_hidden_window(
|
|
101
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
102
|
+
) -> None:
|
|
103
|
+
monkeypatch.setattr(sys, "platform", "win32")
|
|
104
|
+
startup_information = inherited_stream_startup_info()
|
|
105
|
+
assert startup_information is not None
|
|
106
|
+
assert startup_information.dwFlags & subprocess.STARTF_USESHOWWINDOW
|
|
107
|
+
assert startup_information.wShowWindow == subprocess.SW_HIDE
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_other_platforms_report_no_startup_info(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
111
|
+
monkeypatch.setattr(sys, "platform", "linux")
|
|
112
|
+
assert inherited_stream_startup_info() is None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@WINDOWS_ONLY
|
|
116
|
+
def test_a_stream_inheriting_call_site_passes_no_creation_flags(
|
|
117
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
118
|
+
) -> None:
|
|
119
|
+
all_captured_options: dict[str, Any] = {}
|
|
120
|
+
|
|
121
|
+
def fake_run(*_all_arguments: Any, **all_options: Any) -> None:
|
|
122
|
+
all_captured_options.update(all_options)
|
|
123
|
+
|
|
124
|
+
monkeypatch.setattr(sys, "platform", "win32")
|
|
125
|
+
monkeypatch.setattr(subprocess, "run", fake_run)
|
|
126
|
+
subprocess.run(["python", "-m", "pytest"], startupinfo=inherited_stream_startup_info())
|
|
127
|
+
assert "creationflags" not in all_captured_options
|
|
128
|
+
assert all_captured_options["startupinfo"] is not None
|
|
@@ -44,6 +44,8 @@ from hooks_constants.enter_worktree_prefetch_constants import ( # noqa: E402
|
|
|
44
44
|
)
|
|
45
45
|
from hooks_constants.pre_tool_use_stdin import read_hook_input_dictionary_from_stdin # noqa: E402
|
|
46
46
|
|
|
47
|
+
from hooks_constants.subprocess_window import hidden_window_creation_flags
|
|
48
|
+
|
|
47
49
|
|
|
48
50
|
def is_enter_worktree_creation(payload_by_field: dict[str, object]) -> bool:
|
|
49
51
|
"""Return True when this hook invocation is an EnterWorktree creation call.
|
|
@@ -91,6 +93,7 @@ def _run_git_quietly(
|
|
|
91
93
|
timeout=timeout_seconds,
|
|
92
94
|
cwd=repo_directory,
|
|
93
95
|
check=False,
|
|
96
|
+
creationflags=hidden_window_creation_flags(),
|
|
94
97
|
)
|
|
95
98
|
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
96
99
|
return None
|
|
@@ -57,6 +57,8 @@ from hooks_constants.mypy_validator_cache_constants import ( # noqa: E402
|
|
|
57
57
|
)
|
|
58
58
|
from json_file_reader import read_json_object # noqa: E402
|
|
59
59
|
|
|
60
|
+
from hooks_constants.subprocess_window import hidden_window_creation_flags
|
|
61
|
+
|
|
60
62
|
|
|
61
63
|
def load_notification_utils() -> ModuleType | None:
|
|
62
64
|
try:
|
|
@@ -88,6 +90,7 @@ def discover_project_root(target_file: str) -> Path | None:
|
|
|
88
90
|
text=True,
|
|
89
91
|
timeout=GIT_COMMAND_TIMEOUT_SECONDS,
|
|
90
92
|
cwd=str(Path(target_file).parent),
|
|
93
|
+
creationflags=hidden_window_creation_flags(),
|
|
91
94
|
)
|
|
92
95
|
if completed_process.returncode != 0:
|
|
93
96
|
return None
|
|
@@ -493,6 +496,7 @@ def run_mypy(target_file: str, project_root: str) -> tuple[int, str]:
|
|
|
493
496
|
env=os.environ.copy(),
|
|
494
497
|
timeout=MYPY_TIMEOUT_SECONDS,
|
|
495
498
|
cwd=project_root,
|
|
499
|
+
creationflags=hidden_window_creation_flags(),
|
|
496
500
|
)
|
|
497
501
|
|
|
498
502
|
stdout_output = completed_process.stdout.strip()
|
|
@@ -31,6 +31,7 @@ The checks live in `python_style_checks.py`. Shared source-line splitting, funct
|
|
|
31
31
|
| `ruff_integration.py` | Ruff lint integration |
|
|
32
32
|
| `run_all_validators.py` | Pre-push validator orchestration and report |
|
|
33
33
|
| `security_checks.py` | Hardcoded-secret, SQL injection, and XSS checks |
|
|
34
|
+
| `subprocess_window_access.py` | Deployed-path shim re-exporting the hidden-window helpers |
|
|
34
35
|
| `system_temporary_roots.py` | System temp-root membership for staged copies |
|
|
35
36
|
| `todo_checks.py` | TODO/FIXME tracking with issue-reference requirement |
|
|
36
37
|
| `type_safety_checks.py` | Missing type hints and Any usage checks |
|
|
@@ -6,6 +6,8 @@ import sys
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from typing import List
|
|
8
8
|
|
|
9
|
+
from hooks_constants.subprocess_window import hidden_window_creation_flags
|
|
10
|
+
|
|
9
11
|
SUBPROCESS_TIMEOUT_SECONDS = 30
|
|
10
12
|
|
|
11
13
|
|
|
@@ -26,6 +28,7 @@ def get_current_branch() -> str:
|
|
|
26
28
|
text=True,
|
|
27
29
|
check=True,
|
|
28
30
|
timeout=SUBPROCESS_TIMEOUT_SECONDS,
|
|
31
|
+
creationflags=hidden_window_creation_flags(),
|
|
29
32
|
)
|
|
30
33
|
return result.stdout.strip()
|
|
31
34
|
except (FileNotFoundError, subprocess.CalledProcessError, subprocess.TimeoutExpired):
|
|
@@ -55,6 +58,7 @@ def check_draft_pr_state() -> List[Violation]:
|
|
|
55
58
|
text=True,
|
|
56
59
|
check=True,
|
|
57
60
|
timeout=SUBPROCESS_TIMEOUT_SECONDS,
|
|
61
|
+
creationflags=hidden_window_creation_flags(),
|
|
58
62
|
)
|
|
59
63
|
except FileNotFoundError:
|
|
60
64
|
return []
|
|
@@ -16,6 +16,8 @@ from enum import StrEnum
|
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
from typing import Dict, Optional
|
|
18
18
|
|
|
19
|
+
from hooks_constants.subprocess_window import hidden_window_creation_flags
|
|
20
|
+
|
|
19
21
|
VALIDATOR_FILES = [
|
|
20
22
|
"python_style_checks.py",
|
|
21
23
|
"test_safety_checks.py",
|
|
@@ -144,6 +146,7 @@ def check_optional_tool(tool_name: str) -> bool:
|
|
|
144
146
|
check=False,
|
|
145
147
|
capture_output=True,
|
|
146
148
|
text=True,
|
|
149
|
+
creationflags=hidden_window_creation_flags(),
|
|
147
150
|
)
|
|
148
151
|
return result.returncode == 0
|
|
149
152
|
except FileNotFoundError:
|
|
@@ -41,6 +41,8 @@ from pathlib import Path
|
|
|
41
41
|
from types import ModuleType
|
|
42
42
|
from typing import TextIO
|
|
43
43
|
|
|
44
|
+
from subprocess_window_access import hidden_window_creation_flags
|
|
45
|
+
|
|
44
46
|
|
|
45
47
|
def _load_module_from_path(module_name: str, module_path: Path) -> ModuleType:
|
|
46
48
|
"""Load and execute the module found at module_path, under module_name.
|
|
@@ -197,6 +199,7 @@ def run_hosted_command_once_milliseconds(
|
|
|
197
199
|
text=True,
|
|
198
200
|
timeout=_harness_constants.SUBPROCESS_TIMEOUT_SECONDS,
|
|
199
201
|
check=False,
|
|
202
|
+
creationflags=hidden_window_creation_flags(),
|
|
200
203
|
)
|
|
201
204
|
finished_at = time.perf_counter()
|
|
202
205
|
return (finished_at - started_at) * 1000.0
|
|
@@ -9,6 +9,8 @@ from collections.abc import Iterator
|
|
|
9
9
|
from dataclasses import dataclass
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
+
from hooks_constants.subprocess_window import hidden_window_creation_flags
|
|
13
|
+
|
|
12
14
|
_validators_directory = str(Path(__file__).resolve().parent)
|
|
13
15
|
_hooks_directory = str(Path(__file__).resolve().parent.parent)
|
|
14
16
|
|
|
@@ -66,6 +68,7 @@ def check_mypy_available() -> bool:
|
|
|
66
68
|
check=False,
|
|
67
69
|
capture_output=True,
|
|
68
70
|
text=True,
|
|
71
|
+
creationflags=hidden_window_creation_flags(),
|
|
69
72
|
)
|
|
70
73
|
return result.returncode == 0
|
|
71
74
|
except FileNotFoundError:
|
|
@@ -224,6 +227,7 @@ def _run_mypy_subprocess(
|
|
|
224
227
|
text=True,
|
|
225
228
|
cwd=working_directory,
|
|
226
229
|
timeout=timeout_seconds,
|
|
230
|
+
creationflags=hidden_window_creation_flags(),
|
|
227
231
|
)
|
|
228
232
|
except subprocess.TimeoutExpired:
|
|
229
233
|
logger.warning(detached_timeout_skip_message)
|
|
@@ -6,6 +6,8 @@ import sys
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
+
from hooks_constants.subprocess_window import hidden_window_creation_flags
|
|
10
|
+
|
|
9
11
|
_validators_directory = str(Path(__file__).resolve().parent)
|
|
10
12
|
_hooks_directory = str(Path(__file__).resolve().parent.parent)
|
|
11
13
|
|
|
@@ -53,6 +55,7 @@ def check_ruff_available() -> bool:
|
|
|
53
55
|
check=False,
|
|
54
56
|
capture_output=True,
|
|
55
57
|
text=True,
|
|
58
|
+
creationflags=hidden_window_creation_flags(),
|
|
56
59
|
)
|
|
57
60
|
return result.returncode == 0
|
|
58
61
|
except FileNotFoundError:
|
|
@@ -201,6 +204,7 @@ def _staged_ruff_result(
|
|
|
201
204
|
input=staged_content,
|
|
202
205
|
cwd=str(resolved_pyproject.parent),
|
|
203
206
|
env=_ruff_subprocess_environment(),
|
|
207
|
+
creationflags=hidden_window_creation_flags(),
|
|
204
208
|
)
|
|
205
209
|
return RuffResult(
|
|
206
210
|
passed=result.returncode == 0,
|
|
@@ -247,6 +251,7 @@ def _run_native_ruff_check(
|
|
|
247
251
|
capture_output=True,
|
|
248
252
|
text=True,
|
|
249
253
|
env=_ruff_subprocess_environment(),
|
|
254
|
+
creationflags=hidden_window_creation_flags(),
|
|
250
255
|
)
|
|
251
256
|
return RuffResult(
|
|
252
257
|
passed=result.returncode == 0,
|
|
@@ -308,6 +313,7 @@ def run_ruff_fix(all_files: list[Path]) -> RuffResult:
|
|
|
308
313
|
capture_output=True,
|
|
309
314
|
text=True,
|
|
310
315
|
env=_ruff_subprocess_environment(),
|
|
316
|
+
creationflags=hidden_window_creation_flags(),
|
|
311
317
|
)
|
|
312
318
|
return RuffResult(
|
|
313
319
|
passed=result.returncode == 0,
|
|
@@ -60,6 +60,8 @@ from hooks_constants.multi_edit_reconstruction import (
|
|
|
60
60
|
edits_for_tool,
|
|
61
61
|
)
|
|
62
62
|
|
|
63
|
+
from hooks_constants.subprocess_window import hidden_window_creation_flags
|
|
64
|
+
|
|
63
65
|
VALIDATORS_DIR = Path(__file__).parent
|
|
64
66
|
hooks_dir = VALIDATORS_DIR.parent
|
|
65
67
|
package_name = VALIDATORS_DIR.name
|
|
@@ -131,6 +133,7 @@ def invoke_validator_module(module_stem: str, forwarded_file_paths: List[str]) -
|
|
|
131
133
|
text=True,
|
|
132
134
|
cwd=working_directory_string,
|
|
133
135
|
env=environment,
|
|
136
|
+
creationflags=hidden_window_creation_flags(),
|
|
134
137
|
)
|
|
135
138
|
|
|
136
139
|
|
|
@@ -160,6 +163,7 @@ def run_validators_entrypoint_subprocess(
|
|
|
160
163
|
cwd=working_directory_string,
|
|
161
164
|
env=environment,
|
|
162
165
|
input=stdin_text,
|
|
166
|
+
creationflags=hidden_window_creation_flags(),
|
|
163
167
|
)
|
|
164
168
|
|
|
165
169
|
|
|
@@ -347,6 +351,7 @@ def get_project_root() -> Optional[Path]:
|
|
|
347
351
|
["git", "-C", str(hooks_dir), "rev-parse", "--show-toplevel"],
|
|
348
352
|
capture_output=True,
|
|
349
353
|
text=True,
|
|
354
|
+
creationflags=hidden_window_creation_flags(),
|
|
350
355
|
)
|
|
351
356
|
if completed_git_lookup.returncode == 0:
|
|
352
357
|
return Path(completed_git_lookup.stdout.strip())
|
|
@@ -674,6 +679,7 @@ def get_changed_files() -> List[Path]:
|
|
|
674
679
|
["git", "diff", "--cached", "--name-only"],
|
|
675
680
|
capture_output=True,
|
|
676
681
|
text=True,
|
|
682
|
+
creationflags=hidden_window_creation_flags(),
|
|
677
683
|
)
|
|
678
684
|
|
|
679
685
|
files = result.stdout.strip().split("\n") if result.stdout.strip() else []
|
|
@@ -684,6 +690,7 @@ def get_changed_files() -> List[Path]:
|
|
|
684
690
|
["git", "diff", "--name-only", "HEAD~1"],
|
|
685
691
|
capture_output=True,
|
|
686
692
|
text=True,
|
|
693
|
+
creationflags=hidden_window_creation_flags(),
|
|
687
694
|
)
|
|
688
695
|
files = result.stdout.strip().split("\n") if result.stdout.strip() else []
|
|
689
696
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Reach the hidden-window helpers from the scripts this directory deploys.
|
|
2
|
+
|
|
3
|
+
The installer copies this directory into a profile that holds no installed
|
|
4
|
+
Python distribution, and this repository's own tests run the deployed scripts
|
|
5
|
+
under ``python -S -E``, which hides every installed package from the child
|
|
6
|
+
process. On both routes ``hooks_constants`` is reachable only through the
|
|
7
|
+
deployed ``hooks`` directory, so this module puts that directory on
|
|
8
|
+
``sys.path`` and re-exports the helpers a caller asks for::
|
|
9
|
+
|
|
10
|
+
from subprocess_window_access import hidden_window_creation_flags
|
|
11
|
+
|
|
12
|
+
The directory goes on the end of ``sys.path``. ``PathFinder`` runs ahead of
|
|
13
|
+
every editable-install finder, so a trailing entry still selects the deployed
|
|
14
|
+
copy, and the head of ``sys.path`` stays exactly as the caller left it. A
|
|
15
|
+
caller that prepended its own roots keeps them first.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
_subprocess_window_hooks_directory = str(
|
|
24
|
+
Path(__file__).resolve().parents[2] / "hooks"
|
|
25
|
+
)
|
|
26
|
+
if _subprocess_window_hooks_directory not in sys.path:
|
|
27
|
+
sys.path.append(_subprocess_window_hooks_directory)
|
|
28
|
+
|
|
29
|
+
from hooks_constants.subprocess_window import (
|
|
30
|
+
HiddenWindowStartupInfo,
|
|
31
|
+
detached_hidden_window_creation_flags,
|
|
32
|
+
hidden_window_creation_flags,
|
|
33
|
+
inherited_stream_startup_info,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"HiddenWindowStartupInfo",
|
|
38
|
+
"detached_hidden_window_creation_flags",
|
|
39
|
+
"hidden_window_creation_flags",
|
|
40
|
+
"inherited_stream_startup_info",
|
|
41
|
+
]
|
|
@@ -14,6 +14,12 @@ from collections.abc import Mapping
|
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
from types import ModuleType
|
|
16
16
|
|
|
17
|
+
_subprocess_window_hooks_directory = str(Path(__file__).resolve().parents[2] / "hooks")
|
|
18
|
+
if _subprocess_window_hooks_directory not in sys.path:
|
|
19
|
+
sys.path.append(_subprocess_window_hooks_directory)
|
|
20
|
+
|
|
21
|
+
from hooks_constants.subprocess_window import hidden_window_creation_flags
|
|
22
|
+
|
|
17
23
|
NOTIFICATION_UTILS_DIRECTORY = os.path.join(
|
|
18
24
|
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "notification"
|
|
19
25
|
)
|
|
@@ -118,6 +124,7 @@ def is_untracked_in_git(file_path: str) -> bool:
|
|
|
118
124
|
cwd=containing_directory,
|
|
119
125
|
timeout=GIT_LS_FILES_TIMEOUT_SECONDS,
|
|
120
126
|
env=_build_git_command_environment(),
|
|
127
|
+
creationflags=hidden_window_creation_flags(),
|
|
121
128
|
)
|
|
122
129
|
return git_check.returncode != 0
|
|
123
130
|
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
@@ -198,6 +205,7 @@ def _run_command(
|
|
|
198
205
|
capture_output=True,
|
|
199
206
|
text=True,
|
|
200
207
|
timeout=timeout_seconds,
|
|
208
|
+
creationflags=hidden_window_creation_flags(),
|
|
201
209
|
)
|
|
202
210
|
except FileNotFoundError:
|
|
203
211
|
return None, False
|
|
@@ -35,6 +35,8 @@ HOOKS_JSON_PATH = os.path.join(HOOKS_DIRECTORY_PATH, "hooks.json")
|
|
|
35
35
|
POST_TOOL_USE_DISPATCHER_COMMAND_FRAGMENT = "validation/post_tool_use_dispatcher.py"
|
|
36
36
|
UNUSED_IMPORT_SOURCE = "import os\n\n\nVALUE = 1\n"
|
|
37
37
|
HOOK_RUN_TIMEOUT_SECONDS = 60
|
|
38
|
+
WINDOWS_NO_WINDOW_FLAG = 0x08000000
|
|
39
|
+
EXPECTED_HIDDEN_WINDOW_FLAGS = WINDOWS_NO_WINDOW_FLAG if sys.platform == "win32" else 0
|
|
38
40
|
|
|
39
41
|
|
|
40
42
|
def build_fixture_git_environment() -> dict[str, str]:
|
|
@@ -415,3 +417,40 @@ class TestPythonFormatTimeoutBudget:
|
|
|
415
417
|
budgeted_total = auto_formatter_module.budgeted_python_format_seconds()
|
|
416
418
|
|
|
417
419
|
assert budgeted_total < _registered_auto_formatter_timeout()
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _record_creation_flags(
|
|
423
|
+
auto_formatter_module: ModuleType, monkeypatch: pytest.MonkeyPatch
|
|
424
|
+
) -> dict[str, object]:
|
|
425
|
+
all_recorded_keyword_arguments: dict[str, object] = {}
|
|
426
|
+
|
|
427
|
+
def _record(*args: object, **kwargs: object) -> subprocess.CompletedProcess[str]:
|
|
428
|
+
del args
|
|
429
|
+
all_recorded_keyword_arguments.update(kwargs)
|
|
430
|
+
return subprocess.CompletedProcess([], 1, stdout="", stderr="")
|
|
431
|
+
|
|
432
|
+
monkeypatch.setattr(auto_formatter_module.subprocess, "run", _record)
|
|
433
|
+
return all_recorded_keyword_arguments
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def test_untracked_probe_should_start_without_a_console_window(
|
|
437
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
438
|
+
) -> None:
|
|
439
|
+
auto_formatter_module = _load_auto_formatter_module()
|
|
440
|
+
all_recorded_keyword_arguments = _record_creation_flags(auto_formatter_module, monkeypatch)
|
|
441
|
+
|
|
442
|
+
assert auto_formatter_module.is_untracked_in_git(str(tmp_path / "module.py")) is True
|
|
443
|
+
assert all_recorded_keyword_arguments["creationflags"] == EXPECTED_HIDDEN_WINDOW_FLAGS
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def test_formatter_command_should_start_without_a_console_window(
|
|
447
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
448
|
+
) -> None:
|
|
449
|
+
auto_formatter_module = _load_auto_formatter_module()
|
|
450
|
+
all_recorded_keyword_arguments = _record_creation_flags(auto_formatter_module, monkeypatch)
|
|
451
|
+
|
|
452
|
+
auto_formatter_module._run_command(
|
|
453
|
+
["ruff", "format"], str(tmp_path / "module.py"), HOOK_RUN_TIMEOUT_SECONDS
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
assert all_recorded_keyword_arguments["creationflags"] == EXPECTED_HIDDEN_WINDOW_FLAGS
|
package/package.json
CHANGED
package/rules/pstack-models.md
CHANGED
|
@@ -28,9 +28,10 @@ The file contains this shape:
|
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
Offer only model IDs the active native tool accepts. Keep the user's order.
|
|
31
|
-
Treat `inherit-parent` and `auto` as parent inheritance, not model IDs. A
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
Treat `inherit-parent` and `auto` as parent inheritance, not model IDs. A full
|
|
32
|
+
install seeds the Claude file and the Codex file from the package when a host
|
|
33
|
+
file is absent, and leaves an existing file untouched. A different host never
|
|
34
|
+
copies another host's preference file.
|
|
34
35
|
|
|
35
36
|
## Selection before every delegation
|
|
36
37
|
|
|
@@ -10,7 +10,14 @@ from .config.constants import (
|
|
|
10
10
|
GIT_EXECUTABLE,
|
|
11
11
|
)
|
|
12
12
|
from .model import AdvisoryRegistration, LocalCheckout
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
_subprocess_window_hooks_directory = str(Path(__file__).resolve().parents[2] / "hooks")
|
|
17
|
+
if _subprocess_window_hooks_directory not in sys.path:
|
|
18
|
+
sys.path.append(_subprocess_window_hooks_directory)
|
|
19
|
+
|
|
20
|
+
from hooks_constants.subprocess_window import hidden_window_creation_flags
|
|
14
21
|
|
|
15
22
|
|
|
16
23
|
def read_local_checkout(checkout_path: Path) -> LocalCheckout:
|
|
@@ -38,7 +38,14 @@ from automatic_advisory.runner import (
|
|
|
38
38
|
AdvisoryGitHub,
|
|
39
39
|
AutomaticAdvisoryRunner,
|
|
40
40
|
)
|
|
41
|
-
|
|
41
|
+
|
|
42
|
+
_subprocess_window_hooks_directory = str(Path(__file__).resolve().parents[2] / "hooks")
|
|
43
|
+
if _subprocess_window_hooks_directory not in sys.path:
|
|
44
|
+
sys.path.append(_subprocess_window_hooks_directory)
|
|
45
|
+
|
|
46
|
+
from hooks_constants.subprocess_window import (
|
|
47
|
+
detached_hidden_window_creation_flags,
|
|
48
|
+
)
|
|
42
49
|
|
|
43
50
|
|
|
44
51
|
def build_parser() -> argparse.ArgumentParser:
|
|
@@ -134,7 +141,6 @@ def start_polling(settings_path: Path) -> int:
|
|
|
134
141
|
Returns:
|
|
135
142
|
Zero after the child process starts.
|
|
136
143
|
"""
|
|
137
|
-
process_flags = detached_poller_creation_flags()
|
|
138
144
|
resolved_settings_path = settings_path.resolve()
|
|
139
145
|
subprocess.Popen(
|
|
140
146
|
(
|
|
@@ -148,7 +154,7 @@ def start_polling(settings_path: Path) -> int:
|
|
|
148
154
|
stdin=subprocess.DEVNULL,
|
|
149
155
|
stdout=subprocess.DEVNULL,
|
|
150
156
|
stderr=subprocess.DEVNULL,
|
|
151
|
-
creationflags=
|
|
157
|
+
creationflags=detached_hidden_window_creation_flags(),
|
|
152
158
|
start_new_session=True,
|
|
153
159
|
)
|
|
154
160
|
return SUCCESS_EXIT_CODE
|
|
@@ -75,8 +75,6 @@ WINDOWS_EXTENDED_LIMIT_INFORMATION_CLASS = 9
|
|
|
75
75
|
WINDOWS_JOB_KILL_ON_CLOSE_LIMIT = 0x2000
|
|
76
76
|
DEFAULT_CLONE_URL_SUFFIX = ".git"
|
|
77
77
|
POLL_LOCK_DIRECTORY_NAME = ".poll-process"
|
|
78
|
-
CREATE_NEW_PROCESS_GROUP_ATTRIBUTE = "CREATE_NEW_PROCESS_GROUP"
|
|
79
|
-
CREATE_NO_WINDOW_ATTRIBUTE = "CREATE_NO_WINDOW"
|
|
80
78
|
CHILD_MANIFEST_FLAG = "--manifest"
|
|
81
79
|
CHILD_REPOSITORY_FLAG = "--repo"
|
|
82
80
|
CHILD_BASE_FLAG = "--base"
|
|
@@ -26,7 +26,6 @@ from .config.constants import (
|
|
|
26
26
|
)
|
|
27
27
|
from .config.timing import CHILD_CLEANUP_TIMEOUT_SECONDS
|
|
28
28
|
from .model import AdvisoryRegistration, ChildOutcome
|
|
29
|
-
from .window_flags import hidden_window_creation_flags
|
|
30
29
|
from .windows_job import (
|
|
31
30
|
_assign_process,
|
|
32
31
|
_close_handle,
|
|
@@ -34,6 +33,12 @@ from .windows_job import (
|
|
|
34
33
|
_terminate_job,
|
|
35
34
|
)
|
|
36
35
|
|
|
36
|
+
_subprocess_window_hooks_directory = str(Path(__file__).resolve().parents[2] / "hooks")
|
|
37
|
+
if _subprocess_window_hooks_directory not in sys.path:
|
|
38
|
+
sys.path.append(_subprocess_window_hooks_directory)
|
|
39
|
+
|
|
40
|
+
from hooks_constants.subprocess_window import hidden_window_creation_flags
|
|
41
|
+
|
|
37
42
|
|
|
38
43
|
@dataclass(frozen=True)
|
|
39
44
|
class OwnedChildProcess:
|
|
@@ -165,6 +170,7 @@ def _start_posix_child(
|
|
|
165
170
|
text=True,
|
|
166
171
|
env=all_child_environment,
|
|
167
172
|
start_new_session=True,
|
|
173
|
+
creationflags=hidden_window_creation_flags(),
|
|
168
174
|
)
|
|
169
175
|
except OSError:
|
|
170
176
|
return None
|
|
@@ -17,7 +17,15 @@ from .config.constants import (
|
|
|
17
17
|
)
|
|
18
18
|
from .config.timing import GIT_FETCH_TIMEOUT_SECONDS
|
|
19
19
|
from .model import AdvisoryRegistration
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
_subprocess_window_hooks_directory = str(Path(__file__).resolve().parents[2] / "hooks")
|
|
25
|
+
if _subprocess_window_hooks_directory not in sys.path:
|
|
26
|
+
sys.path.append(_subprocess_window_hooks_directory)
|
|
27
|
+
|
|
28
|
+
from hooks_constants.subprocess_window import hidden_window_creation_flags
|
|
21
29
|
|
|
22
30
|
|
|
23
31
|
def _fetch_base(
|
|
@@ -8,6 +8,8 @@ from pr_verification.config.constants import INCOMPLETE_EXIT_CODE
|
|
|
8
8
|
|
|
9
9
|
from .config.constants import WINDOWS_PROCESS_START_SIGNAL
|
|
10
10
|
|
|
11
|
+
from subprocess_window_access import inherited_stream_startup_info
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
def run_owned_child(all_arguments: Sequence[str]) -> int:
|
|
13
15
|
"""Wait for ownership, then run the advisory child.
|
|
@@ -24,6 +26,7 @@ def run_owned_child(all_arguments: Sequence[str]) -> int:
|
|
|
24
26
|
tuple(all_arguments),
|
|
25
27
|
shell=False,
|
|
26
28
|
check=False,
|
|
29
|
+
startupinfo=inherited_stream_startup_info(),
|
|
27
30
|
)
|
|
28
31
|
return completed_process.returncode
|
|
29
32
|
|
|
@@ -131,6 +131,8 @@ from dev_env_scripts_constants.claude_chain_constants import (
|
|
|
131
131
|
UTF8_ENCODING,
|
|
132
132
|
)
|
|
133
133
|
|
|
134
|
+
from subprocess_window_access import hidden_window_creation_flags
|
|
135
|
+
|
|
134
136
|
|
|
135
137
|
def _decode_captured_stream(raw_bytes: bytes, encoding: str, errors: str) -> str:
|
|
136
138
|
"""Decode captured *raw_bytes* with ``text=True`` universal-newline semantics.
|
|
@@ -222,6 +224,7 @@ def _run_captured_subprocess(
|
|
|
222
224
|
else None
|
|
223
225
|
),
|
|
224
226
|
input=input_bytes,
|
|
227
|
+
creationflags=hidden_window_creation_flags(),
|
|
225
228
|
)
|
|
226
229
|
except subprocess.TimeoutExpired as timeout_error:
|
|
227
230
|
_attach_partial_timeout_streams(
|
|
@@ -41,6 +41,8 @@ from dev_env_scripts_constants.gh_artifact_upload_constants import (
|
|
|
41
41
|
UTF8_ENCODING,
|
|
42
42
|
)
|
|
43
43
|
|
|
44
|
+
from subprocess_window_access import hidden_window_creation_flags
|
|
45
|
+
|
|
44
46
|
|
|
45
47
|
class ArtifactUploadError(Exception):
|
|
46
48
|
"""Raised when creating the release or uploading the asset fails."""
|
|
@@ -53,6 +55,7 @@ def _run_gh(all_arguments: list[str]) -> subprocess.CompletedProcess[str]:
|
|
|
53
55
|
text=True,
|
|
54
56
|
encoding=UTF8_ENCODING,
|
|
55
57
|
check=False,
|
|
58
|
+
creationflags=hidden_window_creation_flags(),
|
|
56
59
|
)
|
|
57
60
|
|
|
58
61
|
|
|
@@ -85,6 +85,8 @@ from process_tree_kill import ( # noqa: E402
|
|
|
85
85
|
terminate_process_tree,
|
|
86
86
|
)
|
|
87
87
|
|
|
88
|
+
from subprocess_window_access import hidden_window_creation_flags
|
|
89
|
+
|
|
88
90
|
runner_popen = subprocess.Popen
|
|
89
91
|
|
|
90
92
|
|
|
@@ -361,6 +363,7 @@ def _invoke_process(
|
|
|
361
363
|
encoding=UTF8_ENCODING,
|
|
362
364
|
errors=UTF8_DECODE_ERRORS,
|
|
363
365
|
start_new_session=should_start_new_session(),
|
|
366
|
+
creationflags=hidden_window_creation_flags(),
|
|
364
367
|
)
|
|
365
368
|
except FileNotFoundError:
|
|
366
369
|
return _missing_binary_outcome()
|