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.
Files changed (121) hide show
  1. package/.agents/skills/everything-search/scripts/everything_search.py +11 -0
  2. package/.agents/skills/everything-search/scripts/test_everything_search.py +1 -0
  3. package/.agents/skills/fresh-branch/SKILL.md +1 -0
  4. package/.agents/skills/fresh-branch/scripts/fresh_branch_git_commands.py +3 -0
  5. package/.agents/skills/fresh-branch/scripts/subprocess_window_access.py +43 -0
  6. package/.agents/skills/syncing-submodules/SKILL.md +1 -0
  7. package/.agents/skills/syncing-submodules/scripts/submodule_sync.py +4 -0
  8. package/.agents/skills/syncing-submodules/scripts/subprocess_window_access.py +43 -0
  9. package/.agents/skills/test-runner/SKILL.md +1 -0
  10. package/.agents/skills/test-runner/scripts/preflight_checks.py +5 -0
  11. package/.agents/skills/test-runner/scripts/run_tests.py +11 -0
  12. package/.agents/skills/test-runner/scripts/subprocess_window_access.py +43 -0
  13. package/.agents/skills/test-runner/scripts/test_run_tests.py +9 -7
  14. package/_shared/pr-loop/scripts/check_bugbot_ci.py +3 -0
  15. package/_shared/pr-loop/scripts/check_convergence_gates.py +3 -0
  16. package/_shared/pr-loop/scripts/check_pending_reviews.py +3 -0
  17. package/_shared/pr-loop/scripts/code_rules_gate_parts/added_line_maps.py +3 -0
  18. package/_shared/pr-loop/scripts/code_rules_gate_parts/baseline_import_isolation.py +3 -0
  19. package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +5 -0
  20. package/_shared/pr-loop/scripts/code_rules_gate_parts/git_file_sets.py +4 -0
  21. package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_regression.py +3 -0
  22. package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_running.py +3 -0
  23. package/_shared/pr-loop/scripts/codex_usage_probe.py +3 -0
  24. package/_shared/pr-loop/scripts/copilot_quota.py +3 -0
  25. package/_shared/pr-loop/scripts/fetch_copilot_reviews.py +3 -0
  26. package/_shared/pr-loop/scripts/fix_hookspath.py +7 -0
  27. package/_shared/pr-loop/scripts/post_fix_reply.py +4 -0
  28. package/_shared/pr-loop/scripts/preflight.py +7 -0
  29. package/_shared/pr-loop/scripts/preflight_self_heal.py +5 -0
  30. package/_shared/pr-loop/scripts/run_codex_review.py +3 -0
  31. package/_shared/pr-loop/scripts/subprocess_window_access.py +41 -0
  32. package/_shared/pr-loop/scripts/terminology_sweep.py +5 -0
  33. package/_shared/process-tree/scripts/process_tree_kill.py +3 -0
  34. package/_shared/process-tree/scripts/subprocess_window_access.py +41 -0
  35. package/bin/install-constants.mjs +4 -2
  36. package/bin/install.cursor-rules.test.mjs +57 -15
  37. package/bin/install.mjs +10 -15
  38. package/bin/pstack-model-preferences.claude.json +23 -0
  39. package/hooks/advisory/pr_done_reminder.py +7 -0
  40. package/hooks/advisory/refactor_guard.py +8 -0
  41. package/hooks/advisory/test_pr_done_reminder.py +21 -0
  42. package/hooks/advisory/test_refactor_guard.py +40 -0
  43. package/hooks/blocking/block_main_commit.py +4 -0
  44. package/hooks/blocking/code_rules_comments.py +39 -5
  45. package/hooks/blocking/pii_prevention_blocker_parts/repository_exemption.py +3 -0
  46. package/hooks/blocking/precommit_code_rules_gate.py +3 -0
  47. package/hooks/blocking/tdd_enforcer_parts/content_analysis.py +19 -3
  48. package/hooks/blocking/tdd_enforcer_parts/git_tracking.py +4 -0
  49. package/hooks/blocking/tdd_enforcer_parts/tests/test_content_analysis.py +25 -0
  50. package/hooks/blocking/test_code_rules_enforcer_comment_string_awareness.py +27 -0
  51. package/hooks/blocking/test_code_rules_enforcer_exempt_marker_chained.py +10 -0
  52. package/hooks/git-hooks/git_hooks_constants/verification_notice_constants.py +0 -2
  53. package/hooks/git-hooks/pull_request_handoff.py +3 -0
  54. package/hooks/git-hooks/subprocess_window_access.py +41 -0
  55. package/hooks/git-hooks/test_verification_start.py +4 -0
  56. package/hooks/git-hooks/verification_notice_context.py +3 -0
  57. package/hooks/git-hooks/verification_start.py +7 -8
  58. package/hooks/hooks_constants/code_rules_enforcer_constants.py +2 -0
  59. package/hooks/hooks_constants/subprocess_window.py +141 -0
  60. package/hooks/hooks_constants/subprocess_window_constants.py +9 -0
  61. package/hooks/hooks_constants/test_code_rules_enforcer_constants.py +6 -0
  62. package/hooks/hooks_constants/test_subprocess_window.py +128 -0
  63. package/hooks/lifecycle/enter_worktree_origin_prefetch.py +3 -0
  64. package/hooks/validation/mypy_validator.py +4 -0
  65. package/hooks/validators/README.md +1 -0
  66. package/hooks/validators/git_checks.py +4 -0
  67. package/hooks/validators/health_check.py +3 -0
  68. package/hooks/validators/hook_timing_harness.py +3 -0
  69. package/hooks/validators/mypy_integration.py +4 -0
  70. package/hooks/validators/ruff_integration.py +6 -0
  71. package/hooks/validators/run_all_validators.py +7 -0
  72. package/hooks/validators/subprocess_window_access.py +41 -0
  73. package/hooks/workflow/auto_formatter.py +8 -0
  74. package/hooks/workflow/test_auto_formatter.py +39 -0
  75. package/package.json +1 -1
  76. package/rules/pstack-models.md +4 -3
  77. package/scripts/automatic_advisory/checkout.py +8 -1
  78. package/scripts/automatic_advisory/cli.py +9 -3
  79. package/scripts/automatic_advisory/config/constants.py +0 -2
  80. package/scripts/automatic_advisory/execution.py +7 -1
  81. package/scripts/automatic_advisory/git.py +9 -1
  82. package/scripts/automatic_advisory/process_host.py +3 -0
  83. package/scripts/claude_chain_runner.py +3 -0
  84. package/scripts/gh_artifact_upload.py +3 -0
  85. package/scripts/grok_headless_runner.py +3 -0
  86. package/scripts/grok_patch_artifacts.py +4 -0
  87. package/scripts/grok_worker_preflight.py +3 -0
  88. package/scripts/invoke_code_review.py +3 -0
  89. package/scripts/local_verification/command_runner.py +3 -0
  90. package/scripts/local_verification/git_state.py +3 -0
  91. package/scripts/policy_lint/adapter_detectors.py +48 -2
  92. package/scripts/policy_lint/adapter_pairing.py +150 -2
  93. package/scripts/policy_lint/adapters.py +46 -4
  94. package/scripts/policy_lint/config/approved_test_pairs.py +25 -9
  95. package/scripts/policy_lint/config/constants.py +3 -0
  96. package/scripts/policy_lint/registry.py +1 -1
  97. package/scripts/policy_lint/selection_git.py +11 -0
  98. package/scripts/repository_checks/config/constants.py +5 -0
  99. package/scripts/repository_checks/config/policy_document.py +19 -0
  100. package/scripts/repository_checks/email_exemptions.py +14 -71
  101. package/scripts/repository_checks/path_exemptions.py +35 -0
  102. package/scripts/repository_checks/policy_document.py +120 -0
  103. package/scripts/repository_checks/tracked_secrets.py +27 -4
  104. package/scripts/setup_project_paths.py +3 -0
  105. package/scripts/spawn_grok_batch.py +3 -0
  106. package/scripts/subprocess_window_access.py +41 -0
  107. package/scripts/test_dispatcher_profile_import.py +4 -0
  108. package/scripts/tests/test_automatic_advisory_window_flags.py +17 -11
  109. package/scripts/tests/test_email_exemptions.py +37 -0
  110. package/scripts/tests/test_path_exemptions.py +164 -0
  111. package/scripts/tests/test_policy_document.py +104 -0
  112. package/scripts/tests/test_policy_lint_rules_function_length_scope.py +101 -0
  113. package/scripts/tests/test_policy_lint_rules_pairing.py +375 -0
  114. package/scripts/tests/test_policy_lint_rules_registry.py +116 -0
  115. package/scripts/tests/test_policy_lint_selection.py +34 -0
  116. package/scripts/tests/test_subprocess_window_access.py +118 -0
  117. package/scripts/tests/test_tracked_secrets.py +79 -0
  118. package/scripts/validate_instruction_pairs.py +3 -0
  119. package/scripts/verify_installable_package.py +5 -0
  120. package/scripts/automatic_advisory/window_flags.py +0 -41
  121. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "7.5.0",
3
+ "version": "7.6.0",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
- different host starts with its own confirmed choices. It never copies the Codex
33
- preference file.
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
- from .window_flags import hidden_window_creation_flags
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
- from automatic_advisory.window_flags import detached_poller_creation_flags
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=process_flags,
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
- from .window_flags import hidden_window_creation_flags
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()