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.
Files changed (119) hide show
  1. package/.agents/skills/privacy-hygiene/reference/sweep-procedure.md +1 -1
  2. package/.agents/skills/team-advisor/SKILL.md +2 -2
  3. package/_shared/advisor/reference/identity.md +1 -1
  4. package/_shared/advisor/reference/spawn-walk-log.md +1 -1
  5. package/_shared/advisor/reference/warm-up.md +3 -1
  6. package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +16 -0
  7. package/_shared/advisor/scripts/model_tier_run_validator.py +23 -5
  8. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +85 -6
  9. package/_shared/pr-loop/scripts/terminology_sweep.py +10 -9
  10. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +13 -0
  11. package/bin/install.test.mjs +25 -0
  12. package/docs/high-trust-agent-delivery.md +132 -0
  13. package/docs/references/advisor-tool.md +3 -2
  14. package/docs/second-claude-account.md +50 -0
  15. package/hooks/advisory/refactor_guard.py +2 -2
  16. package/hooks/advisory/test_refactor_guard.py +21 -0
  17. package/hooks/blocking/code_rules_enforcer_test_support.py +10 -0
  18. package/hooks/blocking/code_rules_shared.py +4 -7
  19. package/hooks/blocking/conftest.py +22 -0
  20. package/hooks/blocking/state_description_blocker.py +10 -8
  21. package/hooks/blocking/test_code_rules_enforcer_duplicate_body.py +2 -11
  22. package/hooks/blocking/test_code_rules_enforcer_duplicate_body_hook_routing.py +4 -29
  23. package/hooks/blocking/test_code_rules_enforcer_test_support.py +21 -0
  24. package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias_hook_routing.py +3 -19
  25. package/hooks/blocking/test_code_rules_shared.py +18 -0
  26. package/hooks/blocking/test_conftest.py +35 -0
  27. package/hooks/blocking/test_pii_scanner.py +8 -0
  28. package/hooks/blocking/test_state_description_blocker.py +16 -0
  29. package/hooks/git-hooks/git_hooks_constants/__init__.py +1 -14
  30. package/hooks/git-hooks/pull_request_handoff.py +2 -2
  31. package/hooks/git-hooks/test_config.py +10 -3
  32. package/hooks/git-hooks/test_pull_request_handoff.py +49 -0
  33. package/hooks/git-hooks/test_verification_notice_state.py +50 -0
  34. package/hooks/git-hooks/verification_notice_state.py +3 -1
  35. package/hooks/hooks_constants/code_rules_enforcer_constants.py +13 -5
  36. package/hooks/hooks_constants/pii_prevention_constants.py +1 -0
  37. package/hooks/hooks_constants/refactor_guard_constants.py +2 -0
  38. package/hooks/hooks_constants/session_edit_stage_gate_constants.py +3 -4
  39. package/hooks/hooks_constants/state_description_blocker_constants.py +0 -3
  40. package/hooks/hooks_constants/test_state_description_blocker_constants.py +9 -7
  41. package/hooks/validators/exempt_paths.py +3 -18
  42. package/hooks/validators/test_exempt_paths.py +20 -0
  43. package/hooks/validators/test_python_antipattern_checks.py +14 -0
  44. package/package.json +1 -1
  45. package/rules/agent-merges-its-own-green-pull-request.md +54 -0
  46. package/rules/archiving-agent-config.md +10 -0
  47. package/rules/code-standards.md +1 -0
  48. package/rules/correction-lens.md +134 -0
  49. package/rules/flag-non-breaking-findings.md +11 -0
  50. package/rules/long-horizon-autonomy.md +11 -0
  51. package/rules/no-contrast-framing.md +68 -0
  52. package/rules/review-closure-is-a-check.md +44 -0
  53. package/scripts/Invoke-PesterSuites.ps1 +42 -0
  54. package/scripts/agent_merge_check.py +381 -0
  55. package/scripts/check.ps1 +12 -1
  56. package/scripts/ci/install-playtest.mjs +291 -0
  57. package/scripts/ci/install-playtest.test.mjs +84 -0
  58. package/scripts/ci/installer-lifecycle.mjs +11 -30
  59. package/scripts/ci/installer-lifecycle.test.mjs +14 -0
  60. package/scripts/ci/pstack-command-shim.test.mjs +33 -0
  61. package/scripts/ci/scratch-home-install.mjs +73 -0
  62. package/scripts/ci/scratch-home-install.test.mjs +49 -0
  63. package/scripts/claude_account_choice.py +246 -0
  64. package/scripts/claude_account_profile.py +371 -0
  65. package/scripts/claude_chain_usage.py +49 -5
  66. package/scripts/codex_compat_materializer.py +33 -16
  67. package/scripts/contrast_framing.py +101 -0
  68. package/scripts/dev_env_scripts_constants/agent_merge_check_constants.py +121 -0
  69. package/scripts/dev_env_scripts_constants/claude_account_constants.py +166 -0
  70. package/scripts/dev_env_scripts_constants/contrast_framing_constants.py +82 -0
  71. package/scripts/dev_env_scripts_constants/merge_gate_check_constants.py +23 -0
  72. package/scripts/dev_env_scripts_constants/review_closure_constants.py +241 -0
  73. package/scripts/dev_env_scripts_constants/test_claude_account_constants.py +21 -0
  74. package/scripts/dev_env_scripts_constants/test_code_review_constants.py +12 -0
  75. package/scripts/dev_env_scripts_constants/test_contrast_framing_constants.py +60 -0
  76. package/scripts/durable_post_lint.py +30 -4
  77. package/scripts/followup_cli.py +6 -10
  78. package/scripts/merge_gate_checks.py +236 -0
  79. package/scripts/policy_lint/adapter_contrast_framing.py +59 -0
  80. package/scripts/policy_lint/adapters.py +30 -0
  81. package/scripts/policy_lint/config/constants.py +2 -0
  82. package/scripts/policy_lint/registry.py +5 -0
  83. package/scripts/repository_checks/config/constants.py +12 -0
  84. package/scripts/repository_checks/config/policy_document.py +28 -2
  85. package/scripts/repository_checks/followups.py +54 -0
  86. package/scripts/repository_checks/match_exemptions.py +67 -0
  87. package/scripts/repository_checks/models.py +46 -2
  88. package/scripts/repository_checks/reporting.py +23 -7
  89. package/scripts/repository_checks/tracked_secrets.py +2 -2
  90. package/scripts/repository_policy.py +4 -2
  91. package/scripts/review_closure.py +134 -0
  92. package/scripts/review_closure_github.py +345 -0
  93. package/scripts/review_closure_model.py +360 -0
  94. package/scripts/test_agent_merge_check.py +317 -0
  95. package/scripts/test_claude_account_choice.py +217 -0
  96. package/scripts/test_claude_account_profile.py +220 -0
  97. package/scripts/test_claude_chain_usage.py +40 -0
  98. package/scripts/test_durable_post_lint.py +46 -0
  99. package/scripts/test_followup_cli.py +40 -0
  100. package/scripts/test_merge_gate_checks.py +92 -0
  101. package/scripts/tests/test_code_review_constants.py +78 -0
  102. package/scripts/tests/test_codex_compat_materializer.py +48 -4
  103. package/scripts/tests/test_contrast_framing.py +117 -0
  104. package/scripts/tests/test_followups.py +88 -0
  105. package/scripts/tests/test_match_exemptions.py +291 -0
  106. package/scripts/tests/test_models.py +67 -2
  107. package/scripts/tests/test_package_inventory.py +6 -4
  108. package/scripts/tests/test_pester_suites_run.py +53 -0
  109. package/scripts/tests/test_policy_document.py +39 -1
  110. package/scripts/tests/test_policy_lint_rules_contrast_framing.py +68 -0
  111. package/scripts/tests/test_reporting.py +18 -0
  112. package/scripts/tests/test_repository_policy.py +43 -1
  113. package/scripts/tests/test_review_closure.py +126 -0
  114. package/scripts/tests/test_review_closure_github.py +240 -0
  115. package/scripts/tests/test_review_closure_model.py +299 -0
  116. package/scripts/tests/test_tracked_secrets.py +42 -0
  117. package/hooks/hooks_constants/precommit_code_rules_gate_constants.py +0 -12
  118. package/scripts/repository_checks/email_exemptions.py +0 -39
  119. package/scripts/tests/test_email_exemptions.py +0 -158
@@ -0,0 +1,381 @@
1
+ #!/usr/bin/env python3
2
+ """Report whether the agent driving a pull request may merge it now.
3
+
4
+ An agent that drives a pull request in this repository merges it once the
5
+ repository's own gate passes and no review thread is open. This command reads
6
+ that state from GitHub and prints one verdict line, so the decision rests on
7
+ the pull request's live state rather than on the agent's reading of it.
8
+
9
+ Usage::
10
+
11
+ python3 agent_merge_check.py jl-cmd/claude-dev-env 1442
12
+ -> MERGE jl-cmd/claude-dev-env#1442 ab845eb :: green, no open review
13
+ thread, ready for the agent to merge
14
+
15
+ Exit status is 0 for MERGE, 1 for HOLD, and 2 when the state could not be
16
+ read.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import argparse
22
+ import functools
23
+ import json
24
+ import os
25
+ import sys
26
+ import time
27
+ import urllib.error
28
+ import urllib.request
29
+ from collections.abc import Callable, Mapping, Sequence
30
+
31
+ from dev_env_scripts_constants.agent_merge_check_constants import (
32
+ ACCEPT_HEADER,
33
+ ALL_HOLD_REASONS_BY_STATE,
34
+ ALL_OUTDATED_KEYS,
35
+ ALL_RESOLVED_KEYS,
36
+ ALL_THREAD_NODE_KEYS,
37
+ ALL_TOKEN_ENVIRONMENT_VARIABLES,
38
+ AUTHORIZATION_HEADER,
39
+ BEARER_PREFIX,
40
+ COMMAND_DESCRIPTION,
41
+ CONTENT_TYPE_HEADER,
42
+ DRAFT_HOLD_REASON,
43
+ DRAFT_KEY,
44
+ ERROR_EXIT_CODE,
45
+ GITHUB_ACCEPT_TYPE,
46
+ GITHUB_API_ROOT,
47
+ GITHUB_GRAPHQL_ENDPOINT,
48
+ HEAD_KEY,
49
+ HOLD_EXIT_CODE,
50
+ HOLD_VERDICT_LABEL,
51
+ JSON_CONTENT_TYPE,
52
+ MERGE_EXIT_CODE,
53
+ MERGE_VERDICT_LABEL,
54
+ MERGEABLE_STATE_CLEAN,
55
+ MERGEABLE_STATE_KEY,
56
+ MERGEABLE_STATE_UNKNOWN,
57
+ NAME_VARIABLE,
58
+ NO_SIGN_IN_MESSAGE,
59
+ NUMBER_ARGUMENT_HELP,
60
+ NUMBER_KEY,
61
+ NUMBER_VARIABLE,
62
+ OWNER_VARIABLE,
63
+ PAGE_SIZE_VARIABLE,
64
+ PULL_REQUEST_ENDPOINT_TEMPLATE,
65
+ QUERY_KEY,
66
+ READY_DETAIL,
67
+ REQUEST_TIMEOUT_SECONDS,
68
+ REVIEW_THREAD_PAGE_SIZE,
69
+ REVIEW_THREADS_ENDPOINT_TEMPLATE,
70
+ SETTLE_ATTEMPT_COUNT,
71
+ SETTLE_WAIT_SECONDS,
72
+ SHA_KEY,
73
+ SHORT_SHA_LENGTH,
74
+ SLUG_ARGUMENT_HELP,
75
+ SLUG_SEPARATOR,
76
+ UNKNOWN_STATE_HOLD_TEMPLATE,
77
+ UNRESOLVED_THREAD_QUERY,
78
+ UNRESOLVED_THREADS_HOLD_TEMPLATE,
79
+ UTF8_ENCODING,
80
+ VARIABLES_KEY,
81
+ VERDICT_LINE_TEMPLATE,
82
+ )
83
+
84
+
85
+ class MergeCheckError(Exception):
86
+ """Raised when the pull request state could not be read."""
87
+
88
+
89
+ def hold_reason(
90
+ all_pull_request_fields: Mapping[str, object],
91
+ unresolved_thread_count: int,
92
+ ) -> str | None:
93
+ """Return why this pull request stays open, or None when it may merge.
94
+
95
+ Args:
96
+ all_pull_request_fields: The pull request as the GitHub API reports
97
+ it, carrying its draft flag and its merge state.
98
+ unresolved_thread_count: How many review threads are open on it.
99
+
100
+ Returns:
101
+ The reason text for a draft, for a merge state other than clean, and
102
+ for an open review thread. None when the pull request is ready for
103
+ the agent that drives it to merge it.
104
+ """
105
+ if all_pull_request_fields.get(DRAFT_KEY):
106
+ return DRAFT_HOLD_REASON
107
+ merge_state = all_pull_request_fields.get(MERGEABLE_STATE_KEY)
108
+ if merge_state != MERGEABLE_STATE_CLEAN:
109
+ return ALL_HOLD_REASONS_BY_STATE.get(
110
+ merge_state,
111
+ UNKNOWN_STATE_HOLD_TEMPLATE.format(state=merge_state),
112
+ )
113
+ if unresolved_thread_count > 0:
114
+ return UNRESOLVED_THREADS_HOLD_TEMPLATE.format(count=unresolved_thread_count)
115
+ return None
116
+
117
+
118
+ def verdict_line(
119
+ slug: str,
120
+ all_pull_request_fields: Mapping[str, object],
121
+ reason: str | None,
122
+ ) -> str:
123
+ """Build the one line this command prints for a pull request.
124
+
125
+ Args:
126
+ slug: The repository as ``owner/name``.
127
+ all_pull_request_fields: The pull request the verdict describes.
128
+ reason: The hold reason, or None for a pull request that may merge.
129
+
130
+ Returns:
131
+ A line naming the verdict, the pull request, its head commit, and
132
+ either the hold reason or the ready detail.
133
+ """
134
+ all_head_fields = all_pull_request_fields.get(HEAD_KEY, {})
135
+ head_sha = ""
136
+ if isinstance(all_head_fields, Mapping):
137
+ head_sha = str(all_head_fields.get(SHA_KEY, ""))[:SHORT_SHA_LENGTH]
138
+ return VERDICT_LINE_TEMPLATE.format(
139
+ label=HOLD_VERDICT_LABEL if reason else MERGE_VERDICT_LABEL,
140
+ slug=slug,
141
+ number=all_pull_request_fields.get(NUMBER_KEY),
142
+ sha=head_sha,
143
+ detail=reason or READY_DETAIL,
144
+ )
145
+
146
+
147
+ def count_unresolved_threads(all_thread_records: Sequence[object]) -> int:
148
+ """Count the review threads that still wait on an answer.
149
+
150
+ Args:
151
+ all_thread_records: The review threads GitHub reports for the pull
152
+ request.
153
+
154
+ Returns:
155
+ How many of them are unresolved and still point at live code. An
156
+ outdated thread names code the pull request has since replaced, so it
157
+ holds nothing back.
158
+ """
159
+ return sum(
160
+ 1
161
+ for each_thread in all_thread_records
162
+ if isinstance(each_thread, Mapping)
163
+ and not _any_flag(each_thread, ALL_RESOLVED_KEYS)
164
+ and not _any_flag(each_thread, ALL_OUTDATED_KEYS)
165
+ )
166
+
167
+
168
+ def _any_flag(all_thread_fields: Mapping[str, object], all_keys: Sequence[str]) -> bool:
169
+ return any(all_thread_fields.get(each_key) for each_key in all_keys)
170
+
171
+
172
+ def _github_token() -> str:
173
+ for each_variable in ALL_TOKEN_ENVIRONMENT_VARIABLES:
174
+ token = os.environ.get(each_variable)
175
+ if token:
176
+ return token
177
+ raise MergeCheckError(NO_SIGN_IN_MESSAGE)
178
+
179
+
180
+ def _request_json(
181
+ url: str,
182
+ token: str,
183
+ all_payload_fields: Mapping[str, object] | None,
184
+ ) -> object:
185
+ request = urllib.request.Request(url)
186
+ request.add_header(ACCEPT_HEADER, GITHUB_ACCEPT_TYPE)
187
+ request.add_header(AUTHORIZATION_HEADER, BEARER_PREFIX + token)
188
+ body = None
189
+ if all_payload_fields is not None:
190
+ request.add_header(CONTENT_TYPE_HEADER, JSON_CONTENT_TYPE)
191
+ body = json.dumps(all_payload_fields).encode(UTF8_ENCODING)
192
+ try:
193
+ with urllib.request.urlopen(
194
+ request,
195
+ data=body,
196
+ timeout=REQUEST_TIMEOUT_SECONDS,
197
+ ) as answer:
198
+ return json.loads(answer.read().decode(UTF8_ENCODING))
199
+ except (urllib.error.URLError, ValueError) as failure:
200
+ raise MergeCheckError(str(failure)) from failure
201
+
202
+
203
+ def read_pull_request(slug: str, number: int, token: str) -> Mapping[str, object]:
204
+ """Read one pull request from the GitHub REST API.
205
+
206
+ Args:
207
+ slug: The repository as ``owner/name``.
208
+ number: The pull request number.
209
+ token: The GitHub token the request authenticates with.
210
+
211
+ Returns:
212
+ The pull request fields, carrying its draft flag, its merge state,
213
+ and its head commit.
214
+
215
+ Raises:
216
+ MergeCheckError: The API call failed, or answered with something
217
+ other than a pull request object.
218
+ """
219
+ document = _request_json(
220
+ PULL_REQUEST_ENDPOINT_TEMPLATE.format(
221
+ api_root=GITHUB_API_ROOT,
222
+ slug=slug,
223
+ number=number,
224
+ ),
225
+ token,
226
+ None,
227
+ )
228
+ if not isinstance(document, Mapping):
229
+ raise MergeCheckError(str(document))
230
+ return document
231
+
232
+
233
+ def read_unresolved_thread_count(slug: str, number: int, token: str) -> int:
234
+ """Read how many review threads on a pull request stay open.
235
+
236
+ Two routes carry the same fact. A Claude Code session reaches GitHub
237
+ through a proxy that refuses GraphQL and serves the review threads over a
238
+ REST route of its own, so this reads that route first and falls back to
239
+ the GraphQL query every other caller has.
240
+
241
+ Args:
242
+ slug: The repository as ``owner/name``.
243
+ number: The pull request number.
244
+ token: The GitHub token the request authenticates with.
245
+
246
+ Returns:
247
+ The count of unresolved, current review threads.
248
+
249
+ Raises:
250
+ MergeCheckError: Both routes failed, or both answered with a shape
251
+ that carries no review threads.
252
+ """
253
+ all_thread_records = _thread_records_over_rest(slug, number, token)
254
+ if all_thread_records is None:
255
+ return _read_unresolved_thread_count_over_graphql(slug, number, token)
256
+ return count_unresolved_threads(all_thread_records)
257
+
258
+
259
+ def _thread_records_over_rest(
260
+ slug: str,
261
+ number: int,
262
+ token: str,
263
+ ) -> list[object] | None:
264
+ try:
265
+ document = _request_json(
266
+ REVIEW_THREADS_ENDPOINT_TEMPLATE.format(
267
+ api_root=GITHUB_API_ROOT,
268
+ slug=slug,
269
+ number=number,
270
+ ),
271
+ token,
272
+ None,
273
+ )
274
+ except MergeCheckError:
275
+ return None
276
+ return document if isinstance(document, list) else None
277
+
278
+
279
+ def _thread_query_payload(owner: str, name: str, number: int) -> dict[str, object]:
280
+ return {
281
+ QUERY_KEY: UNRESOLVED_THREAD_QUERY,
282
+ VARIABLES_KEY: {
283
+ OWNER_VARIABLE: owner,
284
+ NAME_VARIABLE: name,
285
+ NUMBER_VARIABLE: number,
286
+ PAGE_SIZE_VARIABLE: REVIEW_THREAD_PAGE_SIZE,
287
+ },
288
+ }
289
+
290
+
291
+ def _read_unresolved_thread_count_over_graphql(
292
+ slug: str,
293
+ number: int,
294
+ token: str,
295
+ ) -> int:
296
+ owner, _, name = slug.partition(SLUG_SEPARATOR)
297
+ document = _request_json(
298
+ GITHUB_GRAPHQL_ENDPOINT,
299
+ token,
300
+ _thread_query_payload(owner, name, number),
301
+ )
302
+ all_thread_records = functools.reduce(
303
+ functools.partial(_field_at, document),
304
+ ALL_THREAD_NODE_KEYS,
305
+ document,
306
+ )
307
+ if not isinstance(all_thread_records, Sequence):
308
+ raise MergeCheckError(str(document))
309
+ return count_unresolved_threads(all_thread_records)
310
+
311
+
312
+ def _field_at(document: object, all_fields: object, key: str) -> object:
313
+ if not isinstance(all_fields, Mapping):
314
+ raise MergeCheckError(str(document))
315
+ return all_fields.get(key)
316
+
317
+
318
+ def read_settled_pull_request(
319
+ slug: str,
320
+ number: int,
321
+ token: str,
322
+ sleep: Callable[[float], None],
323
+ ) -> Mapping[str, object]:
324
+ """Read a pull request whose merge state GitHub has worked out.
325
+
326
+ GitHub reports ``unknown`` while it computes mergeability after a push,
327
+ so this reads again until the state settles.
328
+
329
+ Args:
330
+ slug: The repository as ``owner/name``.
331
+ number: The pull request number.
332
+ token: The GitHub token the request authenticates with.
333
+ sleep: How the caller waits between reads.
334
+
335
+ Returns:
336
+ The fields the last read reported.
337
+
338
+ Raises:
339
+ MergeCheckError: A read failed.
340
+ """
341
+ for each_attempt in range(SETTLE_ATTEMPT_COUNT):
342
+ all_pull_request_fields = read_pull_request(slug, number, token)
343
+ if all_pull_request_fields.get(MERGEABLE_STATE_KEY) != MERGEABLE_STATE_UNKNOWN:
344
+ return all_pull_request_fields
345
+ if each_attempt + 1 < SETTLE_ATTEMPT_COUNT:
346
+ sleep(SETTLE_WAIT_SECONDS)
347
+ return all_pull_request_fields
348
+
349
+
350
+ def main(all_arguments: Sequence[str]) -> int:
351
+ """Print the merge verdict for one pull request.
352
+
353
+ Args:
354
+ all_arguments: The command line, without the program name.
355
+
356
+ Returns:
357
+ Zero when the pull request may merge, one when it holds, and two
358
+ when its state could not be read.
359
+ """
360
+ parser = argparse.ArgumentParser(description=COMMAND_DESCRIPTION)
361
+ parser.add_argument("slug", help=SLUG_ARGUMENT_HELP)
362
+ parser.add_argument("number", type=int, help=NUMBER_ARGUMENT_HELP)
363
+ parsed = parser.parse_args(all_arguments)
364
+ try:
365
+ token = _github_token()
366
+ all_pull_request_fields = read_settled_pull_request(
367
+ parsed.slug, parsed.number, token, time.sleep
368
+ )
369
+ unresolved_thread_count = read_unresolved_thread_count(
370
+ parsed.slug, parsed.number, token
371
+ )
372
+ except MergeCheckError as failure:
373
+ print(failure, file=sys.stderr)
374
+ return ERROR_EXIT_CODE
375
+ reason = hold_reason(all_pull_request_fields, unresolved_thread_count)
376
+ print(verdict_line(parsed.slug, all_pull_request_fields, reason))
377
+ return HOLD_EXIT_CODE if reason else MERGE_EXIT_CODE
378
+
379
+
380
+ if __name__ == "__main__":
381
+ raise SystemExit(main(sys.argv[1:]))
package/scripts/check.ps1 CHANGED
@@ -30,13 +30,16 @@
30
30
  .PARAMETER SkipRepositoryPolicy
31
31
  Skip the committed-tree repository_policy.py run.
32
32
 
33
+ .PARAMETER SkipPester
34
+ Skip the Pester run over the PowerShell suites in scripts/tests.
35
+
33
36
  .PARAMETER CommentPolicyBase
34
37
  Optional git reference used as the comment-policy baseline.
35
38
 
36
39
  .OUTPUTS
37
40
  Per-tool status lines on stdout. Final summary line:
38
41
  CHECK: OK
39
- CHECK: FAILED tools=ruff,repository-policy,mypy,mypy-pr-loop,mypy-process-tree,pytest
42
+ CHECK: FAILED tools=ruff,repository-policy,mypy,mypy-pr-loop,mypy-process-tree,pytest,pester
40
43
  #>
41
44
  [CmdletBinding()]
42
45
  param(
@@ -44,6 +47,7 @@ param(
44
47
  [switch]$SkipMypy,
45
48
  [switch]$SkipRuff,
46
49
  [switch]$SkipRepositoryPolicy,
50
+ [switch]$SkipPester,
47
51
  [string]$CommentPolicyBase
48
52
  )
49
53
 
@@ -54,6 +58,7 @@ $repositoryRoot = Resolve-Path (Join-Path $PSScriptRoot '..' '..' '..')
54
58
  $blockingRoot = Join-Path $hooksRoot 'blocking'
55
59
  $prLoopScriptsRoot = Resolve-Path (Join-Path $PSScriptRoot '..' '_shared' 'pr-loop' 'scripts')
56
60
  $processTreeScriptsRoot = Resolve-Path (Join-Path $PSScriptRoot '..' '_shared' 'process-tree' 'scripts')
61
+ $pesterTestsRoot = Resolve-Path (Join-Path $PSScriptRoot 'tests')
57
62
 
58
63
  $failedTools = @()
59
64
  $firstNonZeroExitCode = 0
@@ -188,6 +193,12 @@ if (-not $SkipTests) {
188
193
  }
189
194
  }
190
195
 
196
+ if (-not $SkipPester) {
197
+ Invoke-Tool -Label 'pester' -Action {
198
+ pwsh -NoProfile -File (Join-Path $PSScriptRoot 'Invoke-PesterSuites.ps1') -TestsRoot $pesterTestsRoot
199
+ }
200
+ }
201
+
191
202
  Write-Host ""
192
203
  if ($failedTools.Count -eq 0) {
193
204
  Write-Host "CHECK: OK" -ForegroundColor Green