claude-dev-env 8.6.0 → 8.7.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 (106) hide show
  1. package/.agents/skills/privacy-hygiene/reference/sweep-procedure.md +1 -1
  2. package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +1 -0
  3. package/_shared/advisor/scripts/model_tier_run_validator.py +3 -2
  4. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +35 -0
  5. package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +4 -0
  6. package/_shared/pr-loop/scripts/terminology_sweep.py +155 -18
  7. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +130 -0
  8. package/bin/install.test.mjs +25 -0
  9. package/docs/high-trust-agent-delivery.md +132 -0
  10. package/hooks/advisory/refactor_guard.py +2 -2
  11. package/hooks/advisory/test_refactor_guard.py +21 -0
  12. package/hooks/blocking/code_rules_enforcer_test_support.py +10 -0
  13. package/hooks/blocking/code_rules_shared.py +4 -7
  14. package/hooks/blocking/conftest.py +22 -0
  15. package/hooks/blocking/state_description_blocker.py +232 -90
  16. package/hooks/blocking/test_code_rules_enforcer_duplicate_body.py +2 -11
  17. package/hooks/blocking/test_code_rules_enforcer_duplicate_body_hook_routing.py +27 -54
  18. package/hooks/blocking/test_code_rules_enforcer_test_support.py +21 -0
  19. package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias_hook_routing.py +3 -19
  20. package/hooks/blocking/test_code_rules_shared.py +18 -0
  21. package/hooks/blocking/test_conftest.py +35 -0
  22. package/hooks/blocking/test_state_description_blocker.py +59 -0
  23. package/hooks/git-hooks/git_hooks_constants/__init__.py +1 -14
  24. package/hooks/git-hooks/pull_request_handoff.py +2 -2
  25. package/hooks/git-hooks/test_config.py +10 -3
  26. package/hooks/git-hooks/test_pull_request_handoff.py +49 -0
  27. package/hooks/git-hooks/test_verification_notice_state.py +50 -0
  28. package/hooks/git-hooks/verification_notice_state.py +3 -1
  29. package/hooks/hooks_constants/code_rules_enforcer_constants.py +13 -5
  30. package/hooks/hooks_constants/refactor_guard_constants.py +2 -0
  31. package/hooks/hooks_constants/session_edit_stage_gate_constants.py +3 -4
  32. package/hooks/hooks_constants/state_description_blocker_constants.py +2 -1
  33. package/hooks/hooks_constants/test_state_description_blocker_constants.py +87 -0
  34. package/hooks/validators/exempt_paths.py +3 -18
  35. package/hooks/validators/test_exempt_paths.py +20 -0
  36. package/hooks/validators/test_python_antipattern_checks.py +14 -0
  37. package/package.json +1 -1
  38. package/rules/agent-merges-its-own-green-pull-request.md +54 -0
  39. package/rules/archiving-agent-config.md +10 -0
  40. package/rules/code-standards.md +1 -0
  41. package/rules/correction-lens.md +134 -0
  42. package/rules/flag-non-breaking-findings.md +11 -0
  43. package/rules/no-contrast-framing.md +68 -0
  44. package/rules/review-closure-is-a-check.md +44 -0
  45. package/scripts/Invoke-PesterSuites.ps1 +42 -0
  46. package/scripts/agent_merge_check.py +381 -0
  47. package/scripts/check.ps1 +12 -1
  48. package/scripts/ci/install-playtest.mjs +291 -0
  49. package/scripts/ci/install-playtest.test.mjs +84 -0
  50. package/scripts/ci/installer-lifecycle.mjs +11 -30
  51. package/scripts/ci/installer-lifecycle.test.mjs +14 -0
  52. package/scripts/ci/pstack-command-shim.test.mjs +33 -0
  53. package/scripts/ci/scratch-home-install.mjs +73 -0
  54. package/scripts/ci/scratch-home-install.test.mjs +49 -0
  55. package/scripts/codex_compat_materializer.py +33 -16
  56. package/scripts/contrast_framing.py +101 -0
  57. package/scripts/dev_env_scripts_constants/agent_merge_check_constants.py +121 -0
  58. package/scripts/dev_env_scripts_constants/contrast_framing_constants.py +82 -0
  59. package/scripts/dev_env_scripts_constants/merge_gate_check_constants.py +23 -0
  60. package/scripts/dev_env_scripts_constants/review_closure_constants.py +241 -0
  61. package/scripts/dev_env_scripts_constants/test_code_review_constants.py +12 -0
  62. package/scripts/dev_env_scripts_constants/test_contrast_framing_constants.py +60 -0
  63. package/scripts/durable_post_lint.py +30 -4
  64. package/scripts/followup_cli.py +6 -10
  65. package/scripts/merge_gate_checks.py +236 -0
  66. package/scripts/policy_lint/adapter_contrast_framing.py +59 -0
  67. package/scripts/policy_lint/adapter_detectors.py +1 -1
  68. package/scripts/policy_lint/adapter_support.py +3 -14
  69. package/scripts/policy_lint/adapters.py +30 -1
  70. package/scripts/policy_lint/config/constants.py +2 -0
  71. package/scripts/policy_lint/registry.py +5 -0
  72. package/scripts/repository_checks/config/constants.py +12 -0
  73. package/scripts/repository_checks/config/policy_document.py +28 -2
  74. package/scripts/repository_checks/followups.py +54 -0
  75. package/scripts/repository_checks/match_exemptions.py +67 -0
  76. package/scripts/repository_checks/models.py +46 -2
  77. package/scripts/repository_checks/reporting.py +23 -7
  78. package/scripts/repository_checks/tracked_secrets.py +2 -2
  79. package/scripts/repository_policy.py +4 -2
  80. package/scripts/review_closure.py +134 -0
  81. package/scripts/review_closure_github.py +345 -0
  82. package/scripts/review_closure_model.py +360 -0
  83. package/scripts/test_agent_merge_check.py +317 -0
  84. package/scripts/test_durable_post_lint.py +46 -0
  85. package/scripts/test_followup_cli.py +40 -0
  86. package/scripts/test_merge_gate_checks.py +92 -0
  87. package/scripts/tests/test_code_review_constants.py +78 -0
  88. package/scripts/tests/test_codex_compat_materializer.py +48 -4
  89. package/scripts/tests/test_contrast_framing.py +117 -0
  90. package/scripts/tests/test_followups.py +88 -0
  91. package/scripts/tests/test_match_exemptions.py +291 -0
  92. package/scripts/tests/test_models.py +67 -2
  93. package/scripts/tests/test_package_inventory.py +6 -4
  94. package/scripts/tests/test_pester_suites_run.py +53 -0
  95. package/scripts/tests/test_policy_document.py +39 -1
  96. package/scripts/tests/test_policy_lint_rules_contrast_framing.py +68 -0
  97. package/scripts/tests/test_policy_lint_rules_engine.py +4 -1
  98. package/scripts/tests/test_reporting.py +18 -0
  99. package/scripts/tests/test_repository_policy.py +43 -1
  100. package/scripts/tests/test_review_closure.py +126 -0
  101. package/scripts/tests/test_review_closure_github.py +240 -0
  102. package/scripts/tests/test_review_closure_model.py +299 -0
  103. package/scripts/tests/test_tracked_secrets.py +42 -0
  104. package/hooks/hooks_constants/precommit_code_rules_gate_constants.py +0 -12
  105. package/scripts/repository_checks/email_exemptions.py +0 -39
  106. package/scripts/tests/test_email_exemptions.py +0 -158
@@ -0,0 +1,54 @@
1
+ # The Agent Merges Its Own Green Pull Request
2
+
3
+ **When this applies:** Any pull request an agent opened or was asked to drive, once its checks report.
4
+
5
+ ## Rule
6
+
7
+ The agent that drives a pull request merges it. A pull request that is green, carries no open review thread, and sits at a merge state of `clean` is merged in the same run that brought it there. Waiting for the owner to type "merge" parks finished work on the person the work was done for.
8
+
9
+ Three things stay with the owner, and nothing else does:
10
+
11
+ - A pull request the owner asked to hold.
12
+ - A change the owner said they want to read first.
13
+ - A repository whose branch rule requires an approving review the agent cannot give.
14
+
15
+ Where the branch rule requires zero approvals and one status check, that check is the gate, and the agent merges on its verdict.
16
+
17
+ Validation is the precondition, and green means every check reported on the exact head commit. A branch rule that requires one status check names the floor a merge needs; a pull request whose other checks are red or still running is held until they report.
18
+
19
+ ## The precondition is mechanical
20
+
21
+ One command prints the verdict:
22
+
23
+ ```
24
+ python packages/claude-dev-env/scripts/agent_merge_check.py <owner>/<name> <number>
25
+ ```
26
+
27
+ It prints `MERGE` and exits 0 when the pull request is ready. It prints `HOLD` with the reason and exits 1 for a draft, for a head behind or conflicting with the base, for a required check that is not passing, for a check still running or red, and for an open review thread. It exits 2 when the state could not be read.
28
+
29
+ Each hold reason names its own repair, and each repair belongs to the agent:
30
+
31
+ | Hold | What the agent does |
32
+ |---|---|
33
+ | Head behind the base | Merge the base branch in and push |
34
+ | Head conflicts with the base | Merge the base branch in, resolve, push |
35
+ | A required check is red | Read the failing check, fix it, push |
36
+ | A check is red or still running | Fix it, or wait for it, then read the verdict again |
37
+ | A review thread is open | Answer it, push the fix, resolve the thread |
38
+ | The pull request is a draft | Mark it ready once the checks pass |
39
+
40
+ ## When a gate elsewhere holds the merge command
41
+
42
+ A session working inside another repository can sit behind that repository's own pre-merge gate, which reads the checkout the session works in and refuses a merge command whatever repository the pull request belongs to. That session hands the merge to the session that owns this repository's pull requests, by message, naming the pull request. The receiving session reads the verdict above and merges. The hand-off carries the work; it never lands on the owner.
43
+
44
+ ## After the merge
45
+
46
+ Delete nothing by hand. The repository deletes the head branch on merge.
47
+
48
+ ## Sibling rules
49
+
50
+ | Rule | Role |
51
+ |---|---|
52
+ | [`git-workflow.md`](git-workflow.md) | Draft first, and confirm each required context fired after the push |
53
+ | [`ci-owns-the-gate.md`](ci-owns-the-gate.md) | The gate runs once, and it runs on CI |
54
+ | [`correction-lens.md`](correction-lens.md) | A correction becomes a control at the highest layer that can hold it |
@@ -43,6 +43,16 @@ Record a row in the archive manifest for each file: why it went, the `git mv`
43
43
  that restores it, and every other edit a restore has to undo. A restore that has
44
44
  to be reconstructed from the diff is not recoverable in any useful sense.
45
45
 
46
+ ## Exempt files
47
+
48
+ The "Never archived" section of `rules-archived/ARCHIVE-MANIFEST.md` names the
49
+ files this procedure leaves alone. Read that section before a prune moves
50
+ anything. A pass that reaches an exempt file stops there and reports which file
51
+ it was, so the prune's other moves still land.
52
+
53
+ `rules/correction-lens.md` is on that list today. Editing an exempt file to
54
+ sharpen it is ordinary work.
55
+
46
56
  ## Make the archive stick
47
57
 
48
58
  Two things do not follow the file on their own.
@@ -27,6 +27,7 @@ BDD is the outer process and TDD is the inner loop: [`bdd.md`](bdd.md) discovers
27
27
 
28
28
  | Concern | Rule file |
29
29
  |---|---|
30
+ | Handling a correction from the user | [`correction-lens.md`](correction-lens.md) |
30
31
  | Task tracking / worker completion | [`workers-done-before-complete.md`](workers-done-before-complete.md) |
31
32
  | Multi-step task list | skill `task-build` (see agents catalog) |
32
33
 
@@ -0,0 +1,134 @@
1
+ # Correction Lens
2
+
3
+ **Standing rule. This file is never archived and never removed.** The permanence
4
+ clause at the end of this document states what that binds.
5
+
6
+ **When this applies:** Every correction the user gives. A "no, do it this way", a
7
+ "stop writing that word", a repeated review comment, a fix the user makes by hand
8
+ after an agent hands work back. Each one is evidence that a control is missing,
9
+ and the correction is handled by building that control.
10
+
11
+ ## Rule
12
+
13
+ A correction runs through five layers, in this order, and the lesson lands at the
14
+ highest layer that can hold it. The change opens in the same run as the
15
+ correction.
16
+
17
+ | Priority | Layer | What holds the lesson | The lesson lands as |
18
+ |---|---|---|---|
19
+ | 1 | Codebase | The mistake is impossible by how the code is written | A type, a signature, a data structure, an API shape, a deleted branch |
20
+ | 2 | Static analysis | A program reads the tree and decides | A lint rule, an enforcer check, a paired test, a CI gate |
21
+ | 3 | Review tooling | A reviewer or a review bot reads the criterion | A `CODE_RULES.md` row, a `.cursor/BUGBOT.md` pointer, a Graphite or BugBot rule |
22
+ | 4 | Skill | An agent follows a procedure | A skill under the agents home |
23
+ | 5 | Style guide | Word choice and prose shape | A row in a `rules/*.md` file or a style document |
24
+
25
+ Layer 1 is the goal every time. A lesson encoded there needs no reader, no run,
26
+ and no agent to remember it. The wrong call has nowhere to live.
27
+
28
+ ## Choosing the layer
29
+
30
+ Three sentences close every correction:
31
+
32
+ 1. The layer chosen.
33
+ 2. Why each higher layer cannot hold this lesson.
34
+ 3. The change opened at the chosen layer, by pull request.
35
+
36
+ A layer holds a lesson when its own test passes:
37
+
38
+ - **Layer 1 holds it** when a type, a signature, or a data structure can make the
39
+ wrong call fail to compile, fail to construct, or fail to exist.
40
+ - **Layer 2 holds it** when a program reading the tree can separate right from
41
+ wrong with no judgment call.
42
+ - **Layer 3 holds it** when a reader needs the criterion in front of them and a
43
+ program cannot decide it.
44
+ - **Layer 4 holds it** when the lesson is a procedure with steps an agent runs.
45
+ - **Layer 5 holds it** when the lesson is word choice or the shape of prose.
46
+
47
+ Effort rules out no layer. A layer is ruled out when it lacks the capability to
48
+ hold the lesson, and the sentence that rules it out says which capability is
49
+ missing. "A lint for this would take a day" leaves layer 2 in play.
50
+
51
+ Two layers often hold one correction. Take both: the shape at layer 1 and the
52
+ check at layer 2 cost one run together and each covers what the other misses.
53
+
54
+ ## A repeated correction moves up a layer
55
+
56
+ The same correction arriving a second time is the measurement. The layer chosen
57
+ the first time was too low. Move the lesson one layer up and say so in the same
58
+ run.
59
+
60
+ A review comment that repeats across pull requests reads the same way. Each
61
+ repetition names the layer below the reviewer as the one that needs the control.
62
+ Count the repetitions.
63
+
64
+ ## Where the control lands
65
+
66
+ A control lands in this package, so every session that loads this environment
67
+ carries it, on any machine, under any model. A control written into one
68
+ project's instructions reaches that project alone, and a control written into a
69
+ chat reply reaches that conversation alone.
70
+
71
+ Memory holds the decision. The control holds the behavior. A correction that
72
+ produced a memory file and nothing else has been recorded and never encoded, so
73
+ the rule still binds. Open the control.
74
+
75
+ ## Trust grows as the controls catch the common mistakes
76
+
77
+ Add agents after the controls catch what agents get wrong, and start from a
78
+ workflow small enough to watch. Each repeated correction becomes a stronger
79
+ control, and the number of agents rises behind it.
80
+ [`docs/high-trust-agent-delivery.md`](../docs/high-trust-agent-delivery.md)
81
+ carries the model this comes from, including the layered-controls picture and
82
+ the inner and outer delivery loops.
83
+
84
+ ## Permanence
85
+
86
+ This file stays in `rules/`. A prune pass, an archive sweep, a consolidation, or
87
+ a rewrite that would move it into `rules-archived/` stops at this line. The
88
+ "Never archived" section of
89
+ [`packages/claude-dev-env/rules-archived/ARCHIVE-MANIFEST.md`](../rules-archived/ARCHIVE-MANIFEST.md)
90
+ names it, and
91
+ [`archiving-agent-config.md`](archiving-agent-config.md) names the same
92
+ exemption from the archiving procedure's side.
93
+
94
+ An edit that sharpens this rule is welcome. The file stays.
95
+
96
+ ## Excerpt for repository-instruction sessions
97
+
98
+ Codex reads its repository `AGENTS.md`; this excerpt supplies the standalone
99
+ contract.
100
+
101
+ ```
102
+ Correction handling for this run, from rules/correction-lens.md.
103
+
104
+ Every correction the user gives becomes a control. Run it through five layers
105
+ and land it at the highest one that can hold it:
106
+
107
+ 1. Codebase. The mistake is impossible by how the code is written: a type, a
108
+ signature, a data structure, an API shape.
109
+ 2. Static analysis. A program reads the tree and decides: a lint rule, an
110
+ enforcer check, a paired test, a CI gate.
111
+ 3. Review tooling. A reviewer or a review bot reads the criterion: a code-rules
112
+ row, a BUGBOT pointer, a Graphite rule.
113
+ 4. Skill. An agent follows a procedure.
114
+ 5. Style guide. Word choice and prose shape.
115
+
116
+ Close the correction with three sentences: the layer chosen, why each higher
117
+ layer cannot hold the lesson, and the change opened at that layer in this run.
118
+ Effort rules out no layer; a missing capability does, and you name which one.
119
+
120
+ The same correction arriving twice means the layer was too low. Move it up one
121
+ layer and say so.
122
+
123
+ Land the control in the shared environment package, so every session carries
124
+ it. A memory file records the decision and encodes nothing.
125
+ ```
126
+
127
+ ## Sibling rules
128
+
129
+ | Rule | Role |
130
+ |---|---|
131
+ | [`flag-non-breaking-findings.md`](flag-non-breaking-findings.md) | A gate blocks on a breaking finding and records a smell |
132
+ | [`code-standards.md`](code-standards.md) | The layer map of contract, pointer, enforcer, lint, session rules |
133
+ | [`archiving-agent-config.md`](archiving-agent-config.md) | How a rule leaves service, and which files are exempt |
134
+ | [`falsify-before-green.md`](falsify-before-green.md) | A new layer-2 check counts once it has run red on a named break |
@@ -97,6 +97,17 @@ loading, so the gate fails. A non-canonical filename and a Git mode other than
97
97
  `SEVERITY_BY_RULE_ID` in that module is the whole declaration. Add a check,
98
98
  add its row.
99
99
 
100
+ `scripts/repository_policy.py` splits the committed-tree checks the same way.
101
+ `SEVERITY_BY_CHECK_ID` in `repository_checks/config/constants.py` carries one
102
+ row per check id, and a check id with no row reads as breaking. The
103
+ `package-inventory` check is the one smell there. A production file whose
104
+ package inventory omits its row still imports and still runs, so the check
105
+ prints its finding with an `advisory:` prefix, records it in the ledger, and
106
+ leaves the tree passing. Every other committed-tree check blocks: a `CLAUDE.md`
107
+ naming a file that does not exist, an env-var row naming a file that never
108
+ reads the variable, a test outside the testpaths allowlist, and a tracked
109
+ secret.
110
+
100
111
  ## Sibling rules
101
112
 
102
113
  | Rule | Role |
@@ -0,0 +1,68 @@
1
+ # No Contrast Framing
2
+
3
+ **When this applies:** Every sentence a person reads. A chat reply, a commit
4
+ message, a pull request title or body, a review comment, an issue, a rule file,
5
+ a document, a plan, a memory file.
6
+
7
+ ## Rule
8
+
9
+ State what is true. Leave the rejected reading out.
10
+
11
+ A contrast defines its subject against something the writer already decided
12
+ against, so the reader carries two readings where one would do, and the second
13
+ one is the writer's own discarded draft. The sentence says more and shows less.
14
+
15
+ Six forms carry the whole ban. Each row names the form the check reports.
16
+
17
+ | Form | Shape | Write this |
18
+ |---|---|---|
19
+ | `trailing-comma-not` | `a diff regression, not shared infrastructure` | `a diff regression` |
20
+ | `corrective-it-is-not` | `it is not a flake, it is a bug` | `a bug`, then the evidence that shows it |
21
+ | `substitution-rather-than` | `park it rather than fix it` | `park it` |
22
+ | `additive-not-just` | `not just for today` | the point the sentence was building toward |
23
+ | `comparative-ranking` | `being precise matters more than trying to cover them` | `be precise about these three` |
24
+ | `substitution-as-opposed-to` | `a warning as opposed to a failure` | `a warning` |
25
+
26
+ A comparison of quantities stays. "The function runs more than 30 lines" counts
27
+ lines. The ban covers the comparison that ranks one course of action over
28
+ another the writer is turning down.
29
+
30
+ Three shapes stay quiet by design. A backticked span or a fenced block carries
31
+ an example, so the check blanks it first. A line opening with `>` quotes
32
+ someone else, whose words are theirs to write. A `, not` clause reports only
33
+ when no bare `not` already sits earlier in the line, so a list of clauses
34
+ reports its first `, not` and stays quiet on the rest, as in
35
+ `a diff regression, not shared code, not a workaround`. A list whose own
36
+ opening word is a bare `not`, as in "not in chat, not in a commit message, not
37
+ in a body", keeps even its first `, not` quiet. A release automation body
38
+ passes the post linter untouched, since the bot builds it from commit
39
+ subjects and reads it back as machine input.
40
+
41
+ When the sentence thins after the contrast comes out, the missing piece is
42
+ evidence. Name it: the failing check, the log line, the measured number, the
43
+ file and the line.
44
+
45
+ ## Where it is enforced
46
+
47
+ | Surface | What runs |
48
+ |---|---|
49
+ | Authored Markdown in the repository | The staged policy lint's `contrast-framing` rule, on every changed file, graded against the file's prior text |
50
+ | A pull request or issue title and body, and a comment | `scripts/durable_post_lint.py`, which every post passes through before it reaches GitHub |
51
+ | A chat reply | The writer, reading the sentence back before sending |
52
+
53
+ The pattern list lives in
54
+ `scripts/dev_env_scripts_constants/contrast_framing_constants.py`, and both
55
+ lints read that one list. A synchronization test requires a row in the table
56
+ above for every form the list carries.
57
+
58
+ A chat reply reaches no check, so the same list is what the writer reads the
59
+ sentence against: a comma followed by `not`, a `rather than`, a `not just`, a
60
+ ranking of one thing over another.
61
+
62
+ ## Sibling rules
63
+
64
+ | Rule | Role |
65
+ |---|---|
66
+ | [`asd-ste100-language.md`](asd-ste100-language.md) | Plain word choice, sentence style, and tone |
67
+ | [`correction-lens.md`](correction-lens.md) | Every correction becomes a control at the highest layer that can hold it |
68
+ | [`research-mode.md`](research-mode.md) | A claim carries its source |
@@ -0,0 +1,44 @@
1
+ # Review Closure Is a Check
2
+
3
+ **When this applies:** Any pull request an agent drives, from the first review comment on it to the merge.
4
+
5
+ ## Rule
6
+
7
+ A review finding on the head is answered before the pull request merges. The agent driving it replies, pushes the fix, or both. The check named `Review closure` reads that state on every push and on every review event, and reports red while a finding waits.
8
+
9
+ One command prints the same verdict:
10
+
11
+ ```
12
+ python packages/claude-dev-env/scripts/review_closure.py <owner>/<name> <number>
13
+ ```
14
+
15
+ It prints `CLOSED` and exits 0 when every finding on the head is answered. It prints `OPEN`, one line per waiting finding, and exits 1. It exits 2 when the state could not be read.
16
+
17
+ ## What closes a finding
18
+
19
+ | The thread | Closed by |
20
+ |---|---|
21
+ | A review comment on code | A push that replaced the code it points at |
22
+ | A review comment on code | A reply from the account driving the pull request |
23
+ | A review comment on code | Resolution, where the comment carries no red circle |
24
+ | A red-circle finding | A reply from the driving account, or a push that replaced the code |
25
+ | A thread the driving account opened | Itself |
26
+ | A blocking `Claude Approvals` row | A push, which moves the head the check reports on |
27
+
28
+ A red circle marks a finding a review states as blocking, so resolution in silence leaves it open. The reply says what changed or why the finding stands, and the reviewer reads it beside the diff.
29
+
30
+ The driving account is the one that opened the pull request. Where the agent comments under a second login, `--driver-login <login>` names it, repeatably.
31
+
32
+ ## Where the check runs
33
+
34
+ `.github/workflows/review-closure.yml` runs it here on a push to a pull request, on a submitted or dismissed review, and on a review comment. Each run reports on the pull request's head commit, so a finding posted after the last push still turns the check red.
35
+
36
+ `Echo-Visuals-Inc/python-automation` runs the same command from its own workflow, against the revision of this package that its workflow pins.
37
+
38
+ ## Sibling rules
39
+
40
+ | Rule | Role |
41
+ |---|---|
42
+ | [`agent-merges-its-own-green-pull-request.md`](agent-merges-its-own-green-pull-request.md) | The agent that drives a pull request merges it once its gate passes |
43
+ | [`git-workflow.md`](git-workflow.md) | Draft first, and confirm each required context fired after the push |
44
+ | [`correction-lens.md`](correction-lens.md) | A correction becomes a control at the highest layer that can hold it |
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env pwsh
2
+ <#
3
+ .SYNOPSIS
4
+ Run every Pester suite under a tests directory and exit on its verdict.
5
+
6
+ .DESCRIPTION
7
+ check.ps1 calls this script as a child process so Invoke-Tool reads a
8
+ process exit code the way it does for ruff, mypy and pytest. Pester is a
9
+ cmdlet and leaves $LASTEXITCODE alone, so running it in-process would
10
+ report every suite as passing.
11
+
12
+ A missing Pester module exits non-zero. A gate that cannot run its check
13
+ has not passed it, and a skip here would leave the suites reporting
14
+ nothing while the tree reads as covered.
15
+
16
+ .PARAMETER TestsRoot
17
+ Directory holding the *.Tests.ps1 suites.
18
+
19
+ .OUTPUTS
20
+ Pester's own detailed output. Exit code 0 when every suite passes.
21
+ #>
22
+ [CmdletBinding()]
23
+ param(
24
+ [Parameter(Mandatory = $true)]
25
+ [string]$TestsRoot
26
+ )
27
+
28
+ $ErrorActionPreference = 'Stop'
29
+
30
+ if (-not (Get-Module -ListAvailable -Name Pester)) {
31
+ Write-Error "Pester is not installed, so the suites under $TestsRoot cannot run."
32
+ exit 1
33
+ }
34
+
35
+ Import-Module Pester -MinimumVersion 5.0
36
+
37
+ $suiteConfiguration = New-PesterConfiguration
38
+ $suiteConfiguration.Run.Path = $TestsRoot
39
+ $suiteConfiguration.Run.Exit = $true
40
+ $suiteConfiguration.Output.Verbosity = 'Detailed'
41
+
42
+ Invoke-Pester -Configuration $suiteConfiguration