claude-dev-env 2.34.0 → 2.36.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 (45) hide show
  1. package/.agents/skills/autoconverge/SKILL.md +19 -15
  2. package/.agents/skills/autoconverge/reference/gotchas.md +3 -3
  3. package/.agents/skills/autoconverge/reference/multi-pr.md +6 -2
  4. package/.agents/skills/autoconverge/workflow/converge.contract.test.mjs +11 -0
  5. package/.agents/skills/autoconverge/workflow/converge.mjs +26 -33
  6. package/.agents/skills/autoconverge/workflow/converge.run-input.test.mjs +26 -2
  7. package/.agents/skills/autoconverge/workflow/converge_multi.mjs +12 -28
  8. package/.agents/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +16 -0
  9. package/bin/cde.mjs +175 -0
  10. package/bin/cde.test.mjs +222 -0
  11. package/bin/install-constants.mjs +4 -0
  12. package/bin/install.cursor-rules.test.mjs +233 -2
  13. package/bin/install.mjs +49 -3
  14. package/bin/pstack-model-preferences.codex.json +23 -0
  15. package/bin/select-pstack-models.test.mjs +378 -0
  16. package/hooks/hooks_constants/tool_names.py +20 -0
  17. package/hooks/validators/fast_save_validators.py +63 -89
  18. package/hooks/validators/run_all_validators.py +16 -8
  19. package/hooks/validators/test_fast_save_validators_policy_lint.py +25 -0
  20. package/hooks/validators/test_run_all_validators_policy_lint.py +52 -0
  21. package/package.json +3 -2
  22. package/rules/pstack-models.md +119 -0
  23. package/scripts/audit-hooks-cli.mjs +54 -0
  24. package/scripts/audit-hooks.mjs +397 -0
  25. package/scripts/cde_lint.py +258 -0
  26. package/scripts/policy_lint/__init__.py +41 -0
  27. package/scripts/policy_lint/adapter_configuration.py +91 -0
  28. package/scripts/policy_lint/adapter_detectors.py +328 -0
  29. package/scripts/policy_lint/adapter_pairing.py +145 -0
  30. package/scripts/policy_lint/adapter_support.py +97 -0
  31. package/scripts/policy_lint/adapters.py +352 -0
  32. package/scripts/policy_lint/config/__init__.py +1 -0
  33. package/scripts/policy_lint/config/constants.py +66 -0
  34. package/scripts/policy_lint/engine.py +224 -0
  35. package/scripts/policy_lint/model.py +387 -0
  36. package/scripts/policy_lint/registry.py +162 -0
  37. package/scripts/policy_lint/render.py +91 -0
  38. package/scripts/policy_lint/selection.py +372 -0
  39. package/scripts/policy_lint/selection_git.py +147 -0
  40. package/scripts/select_pstack_models.mjs +304 -0
  41. package/scripts/tests/test-audit-hooks.mjs +379 -0
  42. package/scripts/tests/test_cde_lint.py +374 -0
  43. package/scripts/tests/test_policy_lint_rules_engine.py +381 -0
  44. package/scripts/tests/test_policy_lint_rules_registry.py +252 -0
  45. package/scripts/tests/test_policy_lint_selection.py +373 -0
@@ -0,0 +1,373 @@
1
+ """Document-selection boundary tests for files, staged, base, and repository sources."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import subprocess
7
+ from pathlib import Path, PurePosixPath
8
+
9
+ import pytest
10
+ from policy_lint import selection as selection_module
11
+ from policy_lint import selection_git
12
+ from policy_lint.config import constants
13
+ from policy_lint.model import (
14
+ ContentOrigin,
15
+ Document,
16
+ DocumentSet,
17
+ LintRequest,
18
+ SelectionKind,
19
+ )
20
+ from policy_lint.selection import SelectionRunFatal, select_documents
21
+
22
+
23
+ def _git_environment() -> dict[str, str]:
24
+ return {
25
+ each_name: each_environment_text
26
+ for each_name, each_environment_text in os.environ.items()
27
+ if not each_name.upper().startswith("GIT_")
28
+ }
29
+
30
+
31
+ def _run_git(repository_root: Path, *all_arguments: str) -> None:
32
+ completed = subprocess.run(
33
+ [constants.GIT_EXECUTABLE, *all_arguments],
34
+ cwd=repository_root,
35
+ check=False,
36
+ capture_output=True,
37
+ text=True,
38
+ env=_git_environment(),
39
+ )
40
+ assert completed.returncode == 0, completed.stderr
41
+
42
+
43
+ def _initialize_repository(repository_root: Path) -> Path:
44
+ _run_git(repository_root, "init", "--quiet", "-b", "main")
45
+ _run_git(repository_root, "config", "user.name", "Selection Tests")
46
+ _run_git(repository_root, "config", "user.email", "selection@example.invalid")
47
+ _run_git(repository_root, "config", "commit.gpgsign", "false")
48
+ _run_git(repository_root, "config", "core.autocrlf", "false")
49
+ return repository_root
50
+
51
+
52
+ def _write_text(repository_root: Path, relative_path: str, text: str) -> None:
53
+ file_path = repository_root / relative_path
54
+ file_path.parent.mkdir(parents=True, exist_ok=True)
55
+ file_path.write_bytes(text.encode(constants.UTF8_ENCODING))
56
+
57
+
58
+ def _commit_text(repository_root: Path, relative_path: str, text: str) -> None:
59
+ _write_text(repository_root, relative_path, text)
60
+ _run_git(repository_root, "add", "--", relative_path)
61
+ _run_git(repository_root, "commit", "--quiet", "-m", "selection fixture")
62
+
63
+
64
+ def _document_for(document_set: DocumentSet, relative_path: str) -> Document:
65
+ for each_document in document_set.documents:
66
+ if each_document.path.as_posix() == relative_path:
67
+ return each_document
68
+ raise AssertionError(f"missing document: {relative_path}")
69
+
70
+
71
+ def test_files_source_should_read_current_worktree_bytes(tmp_path: Path) -> None:
72
+ repository_root = _initialize_repository(tmp_path)
73
+ _commit_text(repository_root, "file.py", "committed\n")
74
+ _write_text(repository_root, "file.py", "worktree\n")
75
+
76
+ document_set = select_documents(
77
+ LintRequest.files(repository_root, [Path("file.py")])
78
+ )
79
+ selected_document = _document_for(document_set, "file.py")
80
+
81
+ assert document_set.selection == SelectionKind.FILES
82
+ assert selected_document.text == "worktree\n"
83
+ assert selected_document.origin == ContentOrigin.WORKTREE
84
+ assert selected_document.prior_text is None
85
+
86
+
87
+ def test_staged_modified_file_should_read_index_bytes_not_worktree(
88
+ tmp_path: Path,
89
+ ) -> None:
90
+ repository_root = _initialize_repository(tmp_path)
91
+ _commit_text(repository_root, "file.py", "committed\n")
92
+ _write_text(repository_root, "file.py", "staged\n")
93
+ _run_git(repository_root, "add", "--", "file.py")
94
+ _write_text(repository_root, "file.py", "worktree\n")
95
+
96
+ document_set = select_documents(LintRequest.staged(repository_root))
97
+ selected_document = _document_for(document_set, "file.py")
98
+
99
+ assert document_set.selection == SelectionKind.STAGED
100
+ assert selected_document.text == "staged\n"
101
+ assert selected_document.prior_text == "committed\n"
102
+ assert selected_document.origin == ContentOrigin.INDEX
103
+ assert selected_document.prior_path is None
104
+ assert document_set.deleted_paths == ()
105
+ assert 1 in (selected_document.changed_lines or frozenset())
106
+
107
+
108
+ def test_staged_added_file_should_have_no_prior_text(tmp_path: Path) -> None:
109
+ repository_root = _initialize_repository(tmp_path)
110
+ _commit_text(repository_root, "keep.py", "keep\n")
111
+ _write_text(repository_root, "new.py", "added\n")
112
+ _run_git(repository_root, "add", "--", "new.py")
113
+
114
+ document_set = select_documents(LintRequest.staged(repository_root))
115
+ selected_document = _document_for(document_set, "new.py")
116
+
117
+ assert selected_document.text == "added\n"
118
+ assert selected_document.prior_text is None
119
+ assert document_set.deleted_paths == ()
120
+ assert selected_document.changed_lines == frozenset({1})
121
+
122
+
123
+ def test_staged_deleted_file_should_record_a_true_delete_only(tmp_path: Path) -> None:
124
+ repository_root = _initialize_repository(tmp_path)
125
+ _commit_text(repository_root, "gone.py", "body\n")
126
+ _run_git(repository_root, "rm", "--quiet", "--", "gone.py")
127
+
128
+ document_set = select_documents(LintRequest.staged(repository_root))
129
+
130
+ assert document_set.documents == ()
131
+ assert document_set.deleted_paths == (PurePosixPath("gone.py"),)
132
+ assert document_set.renamed_paths == ()
133
+
134
+
135
+ def test_staged_renamed_file_should_keep_prior_path_metadata(tmp_path: Path) -> None:
136
+ repository_root = _initialize_repository(tmp_path)
137
+ _commit_text(repository_root, "old.py", "body\n")
138
+ _run_git(repository_root, "mv", "--", "old.py", "new.py")
139
+
140
+ document_set = select_documents(LintRequest.staged(repository_root))
141
+ selected_document = _document_for(document_set, "new.py")
142
+
143
+ assert selected_document.text == "body\n"
144
+ assert selected_document.prior_text == "body\n"
145
+ assert selected_document.prior_path == PurePosixPath("old.py")
146
+ assert document_set.renamed_paths == (
147
+ (PurePosixPath("old.py"), PurePosixPath("new.py")),
148
+ )
149
+ assert document_set.deleted_paths == ()
150
+
151
+
152
+ def test_staged_copied_file_should_not_record_a_rename(
153
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
154
+ ) -> None:
155
+ repository_root = _initialize_repository(tmp_path)
156
+ _commit_text(repository_root, "source.py", "copied-body\n")
157
+ _write_text(repository_root, "copy.py", "copied-body\n")
158
+ _run_git(repository_root, "add", "--", "copy.py")
159
+ original_git_bytes_for = selection_module.git_bytes_for
160
+
161
+ def git_bytes_for_with_copy(
162
+ git_repository_root: Path, all_arguments: tuple[str, ...]
163
+ ) -> bytes:
164
+ if constants.GIT_NAME_STATUS_FLAG in all_arguments:
165
+ return b"C100\0source.py\0copy.py\0"
166
+ return original_git_bytes_for(git_repository_root, all_arguments)
167
+
168
+ monkeypatch.setattr(selection_module, "git_bytes_for", git_bytes_for_with_copy)
169
+ document_set = select_documents(LintRequest.staged(repository_root))
170
+ selected_document = _document_for(document_set, "copy.py")
171
+
172
+ assert selected_document.text == "copied-body\n"
173
+ assert selected_document.prior_path is None
174
+ assert selected_document.prior_text is None
175
+ assert document_set.renamed_paths == ()
176
+ assert document_set.deleted_paths == ()
177
+
178
+
179
+ def test_staged_missing_non_delete_blob_should_raise(
180
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
181
+ ) -> None:
182
+ repository_root = _initialize_repository(tmp_path)
183
+ _commit_text(repository_root, "file.py", "committed\n")
184
+ _write_text(repository_root, "file.py", "staged\n")
185
+ _run_git(repository_root, "add", "--", "file.py")
186
+ original_read_blob = selection_module.read_blob
187
+
188
+ def missing_index_blob(
189
+ git_repository_root: Path, revision: str, relative_path: str
190
+ ) -> str | None:
191
+ if revision == constants.GIT_INDEX_REFERENCE_PREFIX:
192
+ return None
193
+ return original_read_blob(git_repository_root, revision, relative_path)
194
+
195
+ monkeypatch.setattr(selection_module, "read_blob", missing_index_blob)
196
+ with pytest.raises(SelectionRunFatal, match="unavailable"):
197
+ select_documents(LintRequest.staged(repository_root))
198
+
199
+
200
+ def test_git_blob_unexpected_failure_should_raise(
201
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
202
+ ) -> None:
203
+ def failed_git(*_all_arguments: object, **_all_keywords: object) -> subprocess.CompletedProcess[bytes]:
204
+ return subprocess.CompletedProcess(
205
+ args=[constants.GIT_EXECUTABLE],
206
+ returncode=1,
207
+ stdout=b"",
208
+ stderr=b"fatal: Not a valid object name HEAD:file.py",
209
+ )
210
+
211
+ monkeypatch.setattr(selection_git.subprocess, "run", failed_git)
212
+ with pytest.raises(selection_git.GitSelectionError, match="Not a valid object"):
213
+ selection_git.read_blob(tmp_path, "HEAD", "file.py")
214
+
215
+
216
+ def test_unborn_repository_head_should_return_none(tmp_path: Path) -> None:
217
+ repository_root = _initialize_repository(tmp_path)
218
+ assert selection_git.head_revision(repository_root) is None
219
+
220
+
221
+ def test_unexpected_head_failure_should_raise(
222
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
223
+ ) -> None:
224
+ def failed_git(
225
+ *_all_arguments: object, **_all_keywords: object
226
+ ) -> subprocess.CompletedProcess[bytes]:
227
+ return subprocess.CompletedProcess(
228
+ args=[constants.GIT_EXECUTABLE],
229
+ returncode=1,
230
+ stdout=b"",
231
+ stderr=b"fatal: broken HEAD state",
232
+ )
233
+
234
+ monkeypatch.setattr(selection_git.subprocess, "run", failed_git)
235
+ with pytest.raises(selection_git.GitSelectionError, match="broken HEAD"):
236
+ selection_git.head_revision(tmp_path)
237
+
238
+
239
+ def test_base_source_should_compare_merge_base_and_read_worktree_bytes(
240
+ tmp_path: Path,
241
+ ) -> None:
242
+ repository_root = _initialize_repository(tmp_path)
243
+ _commit_text(repository_root, "file.py", "base-text\n")
244
+ _run_git(repository_root, "checkout", "--quiet", "-b", "feature")
245
+ _commit_text(repository_root, "file.py", "feature-committed\n")
246
+ _write_text(repository_root, "file.py", "feature-worktree\n")
247
+
248
+ document_set = select_documents(LintRequest.base(repository_root, "main"))
249
+ selected_document = _document_for(document_set, "file.py")
250
+
251
+ assert document_set.selection == SelectionKind.BASE
252
+ assert selected_document.text == "feature-worktree\n"
253
+ assert selected_document.prior_text == "base-text\n"
254
+ assert selected_document.origin == ContentOrigin.REVISION_DIFF
255
+
256
+
257
+ def test_repository_source_should_read_tracked_worktree_files_only(
258
+ tmp_path: Path,
259
+ ) -> None:
260
+ repository_root = _initialize_repository(tmp_path)
261
+ _commit_text(repository_root, "tracked.py", "tracked\n")
262
+ _write_text(repository_root, "untracked.py", "nope\n")
263
+
264
+ document_set = select_documents(LintRequest.repository(repository_root))
265
+ all_paths = {
266
+ each_document.path.as_posix() for each_document in document_set.documents
267
+ }
268
+ selected_document = _document_for(document_set, "tracked.py")
269
+
270
+ assert document_set.selection == SelectionKind.REPOSITORY
271
+ assert all_paths == {"tracked.py"}
272
+ assert selected_document.text == "tracked\n"
273
+ assert selected_document.origin == ContentOrigin.WORKTREE
274
+
275
+
276
+ def test_prebuilt_document_set_should_keep_repository_relative_paths(
277
+ tmp_path: Path,
278
+ ) -> None:
279
+ repository_root = _initialize_repository(tmp_path)
280
+ document_set = DocumentSet(
281
+ (
282
+ Document(
283
+ PurePosixPath("ok.py"),
284
+ "text\n",
285
+ None,
286
+ None,
287
+ ContentOrigin.EDITOR,
288
+ ),
289
+ ),
290
+ SelectionKind.TEXT,
291
+ repository_root,
292
+ )
293
+
294
+ selected_document_set = select_documents(LintRequest(repository_root, document_set))
295
+
296
+ assert selected_document_set.documents[0].path == PurePosixPath("ok.py")
297
+ assert selected_document_set.selection == SelectionKind.TEXT
298
+
299
+
300
+ def test_files_path_outside_repository_should_raise(tmp_path: Path) -> None:
301
+ repository_root = _initialize_repository(tmp_path)
302
+ outside_path = repository_root.parent / "escape.py"
303
+ with pytest.raises(SelectionRunFatal, match="outside"):
304
+ select_documents(LintRequest.files(repository_root, [outside_path]))
305
+
306
+
307
+ def test_missing_git_should_raise_a_selection_error(
308
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
309
+ ) -> None:
310
+ def missing_git(_repository_root: Path, _all_arguments: tuple[str, ...]) -> bytes:
311
+ raise FileNotFoundError("git")
312
+
313
+ monkeypatch.setattr(selection_module, "git_bytes_for", missing_git)
314
+ with pytest.raises(SelectionRunFatal, match="git"):
315
+ select_documents(LintRequest.files(tmp_path, [Path("file.py")]))
316
+
317
+
318
+ def test_prebuilt_prior_path_outside_repository_should_raise(tmp_path: Path) -> None:
319
+ repository_root = _initialize_repository(tmp_path)
320
+ document_set = DocumentSet(
321
+ (
322
+ Document(
323
+ PurePosixPath("ok.py"),
324
+ "text\n",
325
+ None,
326
+ None,
327
+ ContentOrigin.EDITOR,
328
+ PurePosixPath("../escape"),
329
+ ),
330
+ ),
331
+ SelectionKind.TEXT,
332
+ repository_root,
333
+ )
334
+ with pytest.raises(SelectionRunFatal, match="outside"):
335
+ select_documents(LintRequest(repository_root, document_set))
336
+
337
+
338
+ def test_prebuilt_deleted_path_outside_repository_should_raise(tmp_path: Path) -> None:
339
+ repository_root = _initialize_repository(tmp_path)
340
+ document_set = DocumentSet(
341
+ (),
342
+ SelectionKind.STAGED,
343
+ repository_root,
344
+ deleted_paths=(PurePosixPath("../escape"),),
345
+ )
346
+ with pytest.raises(SelectionRunFatal, match="outside"):
347
+ select_documents(LintRequest(repository_root, document_set))
348
+
349
+
350
+ def test_prebuilt_renamed_path_outside_repository_should_raise(tmp_path: Path) -> None:
351
+ repository_root = _initialize_repository(tmp_path)
352
+ document_set = DocumentSet(
353
+ (),
354
+ SelectionKind.STAGED,
355
+ repository_root,
356
+ renamed_paths=((PurePosixPath("../escape"), PurePosixPath("ok.py")),),
357
+ )
358
+ with pytest.raises(SelectionRunFatal, match="outside"):
359
+ select_documents(LintRequest(repository_root, document_set))
360
+
361
+
362
+ def test_tracked_worktree_document_preserves_git_path_spelling(
363
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
364
+ ) -> None:
365
+ monkeypatch.setattr(
366
+ selection_module,
367
+ "_read_worktree_text",
368
+ lambda _file_path: "body\n",
369
+ )
370
+ selected_document = selection_module._tracked_worktree_document(
371
+ tmp_path, "MiXeD.py"
372
+ )
373
+ assert selected_document.path == PurePosixPath("MiXeD.py")