claude-dev-env 2.25.0 → 2.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/agents/clean-coder.md +95 -33
- package/.agents/agents/code-quality-agent.md +85 -24
- package/.agents/agents/pr-description-writer.md +2 -2
- package/.agents/agents/test_agent_frontmatter.py +626 -0
- package/.agents/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +47 -1
- package/.agents/skills/pr-cleanup/SKILL.md +1 -0
- package/.agents/skills/pr-plain-language-cleanup/SKILL.md +93 -0
- package/.agents/skills/pr-title-description/SKILL.md +92 -0
- package/.agents/skills/source-command-sr-loop/SKILL.md +12 -3
- package/bin/ever-shipped-skills.mjs +2 -0
- package/bin/install.agents-home.test.mjs +199 -8
- package/bin/install.mjs +15 -8
- package/bin/install.test.mjs +82 -2
- package/commands/sr-loop.md +10 -1
- package/docs/codex-compatibility.md +19 -0
- package/hooks/blocking/luna_fast_mode_gate.py +160 -0
- package/hooks/blocking/test_luna_fast_mode_gate.py +211 -0
- package/hooks/hooks.json +10 -5
- package/hooks/hooks_constants/AGENTS.md +1 -1
- package/hooks/hooks_constants/luna_fast_mode_gate_constants.py +45 -0
- package/hooks/hooks_constants/mypy_integration_constants.py +14 -3
- package/hooks/hooks_constants/session_start_injector.py +11 -4
- package/hooks/hooks_constants/test_session_start_injector.py +4 -2
- package/hooks/session/AGENTS.md +2 -2
- package/hooks/session/issue_tracker_session_starter.py +3 -2
- package/hooks/session/orchestrator_auto_starter.py +3 -2
- package/hooks/session/task_list_loop_starter.py +10 -1
- package/hooks/session/test_issue_tracker_session_starter.py +3 -1
- package/hooks/session/test_orchestrator_auto_starter.py +5 -3
- package/hooks/session/test_task_list_loop_starter.py +10 -8
- package/hooks/session/test_untracked_repo_detector.py +7 -5
- package/hooks/session/test_working_style_prompt.py +5 -3
- package/hooks/session/untracked_repo_detector.py +10 -1
- package/hooks/session/working_style_prompt.py +10 -1
- package/hooks/validators/AGENTS.md +2 -1
- package/hooks/validators/conftest.py +21 -4
- package/hooks/validators/mypy_integration.py +77 -16
- package/hooks/validators/run_all_validators.py +2 -35
- package/hooks/validators/system_temporary_roots.py +90 -0
- package/hooks/validators/test_directory_exemption_constants.py +1 -1
- package/hooks/validators/test_mypy_integration.py +169 -0
- package/hooks/validators/test_system_temporary_roots.py +93 -0
- package/package.json +1 -1
- package/scripts/codex_compat_materializer.py +4 -2
- package/scripts/tests/test_codex_compat_materializer.py +2 -2
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Resolve the system temp root that contains a path, if any.
|
|
2
|
+
|
|
3
|
+
Staging copies and pytest basetemp live under ``tempfile.gettempdir()`` or
|
|
4
|
+
under ``TEMP`` / ``TMP`` / ``TMPDIR`` / ``RUNNER_TEMP``. A walk that needs to
|
|
5
|
+
stop at that boundary asks here instead of re-coding membership in each caller.
|
|
6
|
+
|
|
7
|
+
::
|
|
8
|
+
|
|
9
|
+
%TEMP%/pytest-123/x.py -> %TEMP%
|
|
10
|
+
RUNNER_TEMP/detached/x.py (GHA) -> RUNNER_TEMP
|
|
11
|
+
C:/repo/pkg/x.py -> None
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
import tempfile
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
_hooks_directory = str(Path(__file__).resolve().parent.parent)
|
|
22
|
+
if _hooks_directory not in sys.path:
|
|
23
|
+
sys.path.insert(0, _hooks_directory)
|
|
24
|
+
|
|
25
|
+
from validators.config.directory_exemption_constants import ( # noqa: E402
|
|
26
|
+
ALL_SYSTEM_TEMPORARY_ROOT_ENVIRONMENT_VARIABLE_NAMES,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def all_system_temporary_roots() -> tuple[Path, ...]:
|
|
31
|
+
"""Return resolved roots that count as system temporary directories.
|
|
32
|
+
|
|
33
|
+
::
|
|
34
|
+
|
|
35
|
+
gettempdir() plus TEMP / TMP / TMPDIR / RUNNER_TEMP when set.
|
|
36
|
+
|
|
37
|
+
GitHub Actions puts pytest basetemp under ``RUNNER_TEMP``
|
|
38
|
+
(``/home/runner/work/_temp``) while ``tempfile.gettempdir()`` is ``/tmp``.
|
|
39
|
+
Both count so a staging walk and a path-exemption check agree.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Unique resolved directory Paths. Unresolvable candidates are dropped.
|
|
43
|
+
"""
|
|
44
|
+
all_candidate_roots: list[str] = [tempfile.gettempdir()]
|
|
45
|
+
for each_environment_name in ALL_SYSTEM_TEMPORARY_ROOT_ENVIRONMENT_VARIABLE_NAMES:
|
|
46
|
+
environment_value = os.environ.get(each_environment_name)
|
|
47
|
+
if environment_value:
|
|
48
|
+
all_candidate_roots.append(environment_value)
|
|
49
|
+
all_resolved_roots: list[Path] = []
|
|
50
|
+
all_seen_roots: set[Path] = set()
|
|
51
|
+
for each_candidate in all_candidate_roots:
|
|
52
|
+
try:
|
|
53
|
+
resolved_root = Path(each_candidate).resolve()
|
|
54
|
+
except OSError:
|
|
55
|
+
continue
|
|
56
|
+
if resolved_root in all_seen_roots:
|
|
57
|
+
continue
|
|
58
|
+
all_seen_roots.add(resolved_root)
|
|
59
|
+
all_resolved_roots.append(resolved_root)
|
|
60
|
+
return tuple(all_resolved_roots)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def enclosing_system_temporary_root(starting_file: Path) -> Path | None:
|
|
64
|
+
"""Return the innermost system temp root that contains *starting_file*.
|
|
65
|
+
|
|
66
|
+
::
|
|
67
|
+
|
|
68
|
+
%TEMP%/pytest-123/x.py -> %TEMP%
|
|
69
|
+
C:/repo/pkg/x.py -> None
|
|
70
|
+
|
|
71
|
+
When more than one listed root contains the file, the first ancestor that
|
|
72
|
+
is already in the root set wins, so a nested ``RUNNER_TEMP`` stops the walk
|
|
73
|
+
before a broader ``gettempdir()`` ancestor.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
starting_file: The file or directory whose ancestors are bounded.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
The innermost matching root Path, or ``None`` when no listed root
|
|
80
|
+
contains *starting_file*.
|
|
81
|
+
"""
|
|
82
|
+
try:
|
|
83
|
+
resolved_start = starting_file.resolve()
|
|
84
|
+
except OSError:
|
|
85
|
+
return None
|
|
86
|
+
all_root_set = set(all_system_temporary_roots())
|
|
87
|
+
for each_candidate_directory in (resolved_start, *resolved_start.parents):
|
|
88
|
+
if each_candidate_directory in all_root_set:
|
|
89
|
+
return each_candidate_directory
|
|
90
|
+
return None
|
|
@@ -147,7 +147,7 @@ def test_runner_temp_pytest_shaped_path_stages_flat_basename_when_gettempdir_dif
|
|
|
147
147
|
os_gettemp.mkdir()
|
|
148
148
|
monkeypatch.setenv("RUNNER_TEMP", str(runner_temp_root))
|
|
149
149
|
monkeypatch.setattr(
|
|
150
|
-
"validators.
|
|
150
|
+
"validators.system_temporary_roots.tempfile.gettempdir",
|
|
151
151
|
lambda: str(os_gettemp),
|
|
152
152
|
)
|
|
153
153
|
pytest_shaped_target = (
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"""Tests for mypy integration module."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
4
|
+
import subprocess
|
|
3
5
|
from pathlib import Path
|
|
4
6
|
from unittest.mock import patch
|
|
5
7
|
|
|
6
8
|
import pytest
|
|
7
9
|
|
|
10
|
+
from . import mypy_integration as mypy_integration_module
|
|
8
11
|
from .mypy_integration import (
|
|
9
12
|
MypyResult,
|
|
10
13
|
check_mypy_available,
|
|
@@ -30,6 +33,63 @@ def _write_config_importer(script_path: Path) -> None:
|
|
|
30
33
|
)
|
|
31
34
|
|
|
32
35
|
|
|
36
|
+
def test_find_module_resolution_root_ignores_git_above_system_temp(
|
|
37
|
+
tmp_path: Path,
|
|
38
|
+
) -> None:
|
|
39
|
+
"""A temp staging file does not inherit ``.git`` from a parent of ``%TEMP%``.
|
|
40
|
+
|
|
41
|
+
::
|
|
42
|
+
|
|
43
|
+
%TEMP%/pytest-.../detached/module.py
|
|
44
|
+
C:/Users/<you>/.git exists above %TEMP%
|
|
45
|
+
flag: walk returns the home repo -> mypy cwd=home, torch stubs, 30s timeout
|
|
46
|
+
ok: walk stops at the temp root -> None
|
|
47
|
+
"""
|
|
48
|
+
nested_file = tmp_path / "detached" / "module.py"
|
|
49
|
+
nested_file.parent.mkdir()
|
|
50
|
+
nested_file.write_text("sample_number: int = 1\n", encoding="utf-8")
|
|
51
|
+
assert find_module_resolution_root(nested_file) is None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_find_module_resolution_root_still_sees_git_inside_temp(tmp_path: Path) -> None:
|
|
55
|
+
"""A repo created inside the temp tree is still a project root."""
|
|
56
|
+
nested_repo = tmp_path / "inner_repo"
|
|
57
|
+
nested_file = nested_repo / "pkg" / "module.py"
|
|
58
|
+
nested_file.parent.mkdir(parents=True)
|
|
59
|
+
(nested_repo / ".git").mkdir()
|
|
60
|
+
nested_file.write_text("sample_number: int = 1\n", encoding="utf-8")
|
|
61
|
+
assert find_module_resolution_root(nested_file) == nested_repo
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_find_module_resolution_root_stops_at_runner_temp_when_gettempdir_differs(
|
|
65
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
66
|
+
) -> None:
|
|
67
|
+
"""A RUNNER_TEMP staging file does not inherit ``.git`` above that root.
|
|
68
|
+
|
|
69
|
+
::
|
|
70
|
+
|
|
71
|
+
RUNNER_TEMP/detached/module.py, gettempdir -> sibling os_gettemp
|
|
72
|
+
tmp_path/.git sits above RUNNER_TEMP
|
|
73
|
+
flag: gettempdir-only walk returns tmp_path
|
|
74
|
+
ok: walk stops at RUNNER_TEMP -> None
|
|
75
|
+
"""
|
|
76
|
+
runner_temp_root = tmp_path / "runner_temp"
|
|
77
|
+
os_gettemp = tmp_path / "os_gettemp"
|
|
78
|
+
runner_temp_root.mkdir()
|
|
79
|
+
os_gettemp.mkdir()
|
|
80
|
+
(tmp_path / ".git").mkdir()
|
|
81
|
+
monkeypatch.setenv("RUNNER_TEMP", str(runner_temp_root))
|
|
82
|
+
monkeypatch.setattr(
|
|
83
|
+
"validators.system_temporary_roots.tempfile.gettempdir",
|
|
84
|
+
lambda: str(os_gettemp),
|
|
85
|
+
)
|
|
86
|
+
nested_file = runner_temp_root / "detached" / "module.py"
|
|
87
|
+
nested_file.parent.mkdir()
|
|
88
|
+
nested_file.write_text("sample_number: int = 1\n", encoding="utf-8")
|
|
89
|
+
|
|
90
|
+
assert find_module_resolution_root(nested_file) is None
|
|
91
|
+
|
|
92
|
+
|
|
33
93
|
def test_find_module_resolution_root_returns_git_marked_ancestor(tmp_path: Path) -> None:
|
|
34
94
|
target_repo = tmp_path / "git_repo"
|
|
35
95
|
nested_directory = target_repo / "src" / "deep"
|
|
@@ -165,6 +225,115 @@ def test_run_mypy_check_accepts_relative_path_under_nested_root(
|
|
|
165
225
|
assert mypy_result.passed, mypy_result.output
|
|
166
226
|
|
|
167
227
|
|
|
228
|
+
def test_run_mypy_check_does_not_report_imported_sibling_on_detached_file(
|
|
229
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
230
|
+
) -> None:
|
|
231
|
+
"""A detached gate file does not inherit type errors from an imported sibling.
|
|
232
|
+
|
|
233
|
+
::
|
|
234
|
+
|
|
235
|
+
detached/importer.py -> from sibling import broken_value
|
|
236
|
+
detached/sibling.py -> broken_value: int = "not an integer"
|
|
237
|
+
flag: follow-imports=normal reports sibling.py and fails the gate file
|
|
238
|
+
ok: follow-imports=skip grades importer.py only -> passed
|
|
239
|
+
|
|
240
|
+
The PreToolUse gate stages one file under a temp directory with no project
|
|
241
|
+
root. Following imports there loads site-packages (torch stubs) and sibling
|
|
242
|
+
modules, and the 30s hook budget expires.
|
|
243
|
+
"""
|
|
244
|
+
if not check_mypy_available():
|
|
245
|
+
pytest.skip("mypy is not installed")
|
|
246
|
+
|
|
247
|
+
detached_directory = tmp_path / "detached_gate_dir"
|
|
248
|
+
detached_directory.mkdir()
|
|
249
|
+
(detached_directory / "sibling.py").write_text(
|
|
250
|
+
"broken_value: int = 'not an integer'\n",
|
|
251
|
+
encoding="utf-8",
|
|
252
|
+
)
|
|
253
|
+
importer_file = detached_directory / "importer.py"
|
|
254
|
+
importer_file.write_text(
|
|
255
|
+
"from sibling import broken_value\n\n"
|
|
256
|
+
"copied_value: int = broken_value\n",
|
|
257
|
+
encoding="utf-8",
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
monkeypatch.chdir(tmp_path)
|
|
261
|
+
mypy_result = run_mypy_check([importer_file])
|
|
262
|
+
|
|
263
|
+
assert mypy_result.passed, mypy_result.output
|
|
264
|
+
assert "sibling.py" not in mypy_result.output
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def test_run_mypy_check_reports_imported_sibling_on_rooted_file(
|
|
268
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
269
|
+
) -> None:
|
|
270
|
+
"""A rooted file still follows first-party imports and reports sibling errors.
|
|
271
|
+
|
|
272
|
+
::
|
|
273
|
+
|
|
274
|
+
rooted_repo/.git + importer.py -> from sibling import broken_value
|
|
275
|
+
rooted_repo/sibling.py -> broken_value: int = "not an integer"
|
|
276
|
+
ok: follow-imports=normal reports sibling.py -> failed
|
|
277
|
+
flag: --follow-imports=skip on rooted files hides sibling.py -> passed
|
|
278
|
+
|
|
279
|
+
Skip applies only to detached staging copies. Applying it to a repo file
|
|
280
|
+
would drop first-party type errors the in-repo path must still see.
|
|
281
|
+
"""
|
|
282
|
+
if not check_mypy_available():
|
|
283
|
+
pytest.skip("mypy is not installed")
|
|
284
|
+
|
|
285
|
+
rooted_repo = tmp_path / "rooted_repo"
|
|
286
|
+
rooted_repo.mkdir()
|
|
287
|
+
(rooted_repo / ".git").mkdir()
|
|
288
|
+
(rooted_repo / "sibling.py").write_text(
|
|
289
|
+
"broken_value: int = 'not an integer'\n",
|
|
290
|
+
encoding="utf-8",
|
|
291
|
+
)
|
|
292
|
+
importer_file = rooted_repo / "importer.py"
|
|
293
|
+
importer_file.write_text(
|
|
294
|
+
"from sibling import broken_value\n\n"
|
|
295
|
+
"copied_value: int = broken_value\n",
|
|
296
|
+
encoding="utf-8",
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
monkeypatch.chdir(tmp_path)
|
|
300
|
+
mypy_result = run_mypy_check([importer_file])
|
|
301
|
+
|
|
302
|
+
assert not mypy_result.passed
|
|
303
|
+
assert "sibling.py" in mypy_result.output
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def test_run_mypy_check_skips_when_detached_mypy_times_out(
|
|
307
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
|
|
308
|
+
) -> None:
|
|
309
|
+
"""A detached mypy that exceeds its timeout does not fail the file.
|
|
310
|
+
|
|
311
|
+
::
|
|
312
|
+
|
|
313
|
+
detached/module.py, subprocess.run raises TimeoutExpired
|
|
314
|
+
ok: passed=True and the skip message is in the output
|
|
315
|
+
flag: TimeoutExpired propagates and the PreToolUse hook dies
|
|
316
|
+
"""
|
|
317
|
+
detached_file = tmp_path / "detached" / "module.py"
|
|
318
|
+
detached_file.parent.mkdir()
|
|
319
|
+
detached_file.write_text("sample_number: int = 1\n", encoding="utf-8")
|
|
320
|
+
|
|
321
|
+
monkeypatch.setattr(
|
|
322
|
+
mypy_integration_module, "check_mypy_available", lambda: True
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
def raise_timeout(*_args: object, **_kwargs: object) -> None:
|
|
326
|
+
raise subprocess.TimeoutExpired(cmd="mypy", timeout=1)
|
|
327
|
+
|
|
328
|
+
monkeypatch.setattr(mypy_integration_module.subprocess, "run", raise_timeout)
|
|
329
|
+
with caplog.at_level(logging.WARNING):
|
|
330
|
+
mypy_result = run_mypy_check([detached_file])
|
|
331
|
+
|
|
332
|
+
assert mypy_result.passed
|
|
333
|
+
assert "timed out on a detached file" in mypy_result.output
|
|
334
|
+
assert "timed out on a detached file" in caplog.text
|
|
335
|
+
|
|
336
|
+
|
|
168
337
|
def test_run_mypy_check_applies_config_resolved_from_config_source_path(
|
|
169
338
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
170
339
|
) -> None:
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Tests for the shared system-temp root membership helper."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from .system_temporary_roots import (
|
|
8
|
+
all_system_temporary_roots,
|
|
9
|
+
enclosing_system_temporary_root,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_all_system_temporary_roots_includes_runner_temp_when_set(
|
|
14
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
15
|
+
) -> None:
|
|
16
|
+
"""RUNNER_TEMP joins gettempdir so GHA basetemp counts as system temp."""
|
|
17
|
+
runner_temp_root = tmp_path / "runner_temp"
|
|
18
|
+
os_gettemp = tmp_path / "os_gettemp"
|
|
19
|
+
runner_temp_root.mkdir()
|
|
20
|
+
os_gettemp.mkdir()
|
|
21
|
+
monkeypatch.setenv("RUNNER_TEMP", str(runner_temp_root))
|
|
22
|
+
monkeypatch.setattr(
|
|
23
|
+
"validators.system_temporary_roots.tempfile.gettempdir",
|
|
24
|
+
lambda: str(os_gettemp),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
all_roots = all_system_temporary_roots()
|
|
28
|
+
|
|
29
|
+
assert os_gettemp.resolve() in all_roots
|
|
30
|
+
assert runner_temp_root.resolve() in all_roots
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_enclosing_system_temporary_root_returns_gettempdir_for_file_under_it(
|
|
34
|
+
tmp_path: Path,
|
|
35
|
+
) -> None:
|
|
36
|
+
"""A pytest tmp_path file sits under the process temp root."""
|
|
37
|
+
nested_file = tmp_path / "detached" / "module.py"
|
|
38
|
+
nested_file.parent.mkdir()
|
|
39
|
+
nested_file.write_text("sample_number: int = 1\n", encoding="utf-8")
|
|
40
|
+
|
|
41
|
+
enclosing_root = enclosing_system_temporary_root(nested_file)
|
|
42
|
+
|
|
43
|
+
assert enclosing_root is not None
|
|
44
|
+
assert nested_file.resolve().is_relative_to(enclosing_root)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_enclosing_system_temporary_root_uses_runner_temp_when_gettempdir_differs(
|
|
48
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
49
|
+
) -> None:
|
|
50
|
+
"""GHA basetemp lives under RUNNER_TEMP while gettempdir is often another root.
|
|
51
|
+
|
|
52
|
+
::
|
|
53
|
+
|
|
54
|
+
RUNNER_TEMP/detached/module.py, gettempdir -> sibling os_gettemp
|
|
55
|
+
ok: enclosing root is RUNNER_TEMP
|
|
56
|
+
flag: gettempdir-only helper returns None and the mypy walk climbs out
|
|
57
|
+
"""
|
|
58
|
+
runner_temp_root = tmp_path / "runner_temp"
|
|
59
|
+
os_gettemp = tmp_path / "os_gettemp"
|
|
60
|
+
runner_temp_root.mkdir()
|
|
61
|
+
os_gettemp.mkdir()
|
|
62
|
+
monkeypatch.setenv("RUNNER_TEMP", str(runner_temp_root))
|
|
63
|
+
monkeypatch.setattr(
|
|
64
|
+
"validators.system_temporary_roots.tempfile.gettempdir",
|
|
65
|
+
lambda: str(os_gettemp),
|
|
66
|
+
)
|
|
67
|
+
nested_file = runner_temp_root / "detached" / "module.py"
|
|
68
|
+
nested_file.parent.mkdir()
|
|
69
|
+
nested_file.write_text("sample_number: int = 1\n", encoding="utf-8")
|
|
70
|
+
|
|
71
|
+
assert enclosing_system_temporary_root(nested_file) == runner_temp_root.resolve()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_enclosing_system_temporary_root_returns_none_outside_every_temp_root(
|
|
75
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
76
|
+
) -> None:
|
|
77
|
+
"""A path outside gettempdir and the temp env vars is not a staging copy."""
|
|
78
|
+
os_gettemp = tmp_path / "os_gettemp"
|
|
79
|
+
project_root = tmp_path / "project"
|
|
80
|
+
os_gettemp.mkdir()
|
|
81
|
+
project_root.mkdir()
|
|
82
|
+
monkeypatch.delenv("RUNNER_TEMP", raising=False)
|
|
83
|
+
monkeypatch.delenv("TEMP", raising=False)
|
|
84
|
+
monkeypatch.delenv("TMP", raising=False)
|
|
85
|
+
monkeypatch.delenv("TMPDIR", raising=False)
|
|
86
|
+
monkeypatch.setattr(
|
|
87
|
+
"validators.system_temporary_roots.tempfile.gettempdir",
|
|
88
|
+
lambda: str(os_gettemp),
|
|
89
|
+
)
|
|
90
|
+
project_file = project_root / "module.py"
|
|
91
|
+
project_file.write_text("sample_number: int = 1\n", encoding="utf-8")
|
|
92
|
+
|
|
93
|
+
assert enclosing_system_temporary_root(project_file) is None
|
package/package.json
CHANGED
|
@@ -25,7 +25,7 @@ reparse_point_attribute_name = "FILE_ATTRIBUTE_REPARSE_POINT"
|
|
|
25
25
|
manifest_indentation_width = 2
|
|
26
26
|
publish_plan_max_positional_arguments = 3
|
|
27
27
|
publish_plan_failure_injector_position = 2
|
|
28
|
-
frontmatter_unsupported_fields = ("tools", "model", "color")
|
|
28
|
+
frontmatter_unsupported_fields = ("tools", "model", "color", "disable-model-invocation")
|
|
29
29
|
instruction_alias_filenames = frozenset({"AGENTS.md", "CLAUDE.md"})
|
|
30
30
|
failure_blast_radius_rule_relative_path = "rules/failure-blast-radius.md"
|
|
31
31
|
codex_instruction_target_path = "AGENTS.md"
|
|
@@ -152,7 +152,9 @@ report_categories = (
|
|
|
152
152
|
"stale_managed", "deleted", "unsupported", "conflicted", "errors",
|
|
153
153
|
)
|
|
154
154
|
report_categories_public_name = "REPORT_CATEGORIES"
|
|
155
|
-
frontmatter_allowed_fields = {
|
|
155
|
+
frontmatter_allowed_fields = {
|
|
156
|
+
"name", "description", "tools", "model", "color", "disable-model-invocation"
|
|
157
|
+
}
|
|
156
158
|
line_separator = "\n"
|
|
157
159
|
comma_separator = ", "
|
|
158
160
|
|
|
@@ -40,8 +40,8 @@ def test_conversion_escapes_toml_content() -> None:
|
|
|
40
40
|
def test_malformed_and_unsupported_frontmatter() -> None:
|
|
41
41
|
with pytest.raises(MaterializerError):
|
|
42
42
|
parse_frontmatter(Path("bad.md"), "---\nname: x\n", "bad.md")
|
|
43
|
-
agent = parse_frontmatter(Path("ok.md"), "---\nname: x\ndescription: y\nmodel: sonnet\ncolor: blue\n---\n", "ok.md")
|
|
44
|
-
assert agent.unsupported == ("color", "model")
|
|
43
|
+
agent = parse_frontmatter(Path("ok.md"), "---\nname: x\ndescription: y\nmodel: sonnet\ncolor: blue\ndisable-model-invocation: true\n---\n", "ok.md")
|
|
44
|
+
assert agent.unsupported == ("color", "disable-model-invocation", "model")
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
@pytest.mark.parametrize(
|