@xenonbyte/req-2-plan 0.4.2 → 0.4.3
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/package.json
CHANGED
|
@@ -6,6 +6,11 @@ import os
|
|
|
6
6
|
from dataclasses import asdict, dataclass, field
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
+
try:
|
|
10
|
+
import tomllib # Python 3.11+
|
|
11
|
+
except ImportError: # pragma: no cover - older interpreters: pyproject parsing degrades to a no-op
|
|
12
|
+
tomllib = None
|
|
13
|
+
|
|
9
14
|
from tools.workflow_cli.repo_baseline import SKIP_DIRS, scan_repo_baseline
|
|
10
15
|
|
|
11
16
|
_CONFIG_NAMES = {
|
|
@@ -39,6 +44,41 @@ def _append_npm_dependencies(pack: ProjectContextPack, dependencies: object, *,
|
|
|
39
44
|
pack.dependencies.append(dep)
|
|
40
45
|
|
|
41
46
|
|
|
47
|
+
def _append_pyproject_facts(pack: ProjectContextPack, repo_path: Path) -> None:
|
|
48
|
+
"""Collect PEP 621 [project] dependencies and a pytest signal from pyproject.toml.
|
|
49
|
+
|
|
50
|
+
Poetry/PDM private tables are out of scope; without tomllib (Python < 3.11)
|
|
51
|
+
this is a no-op, matching the pack's best-effort scan semantics."""
|
|
52
|
+
pyproject = repo_path / "pyproject.toml"
|
|
53
|
+
if tomllib is None or not pyproject.exists():
|
|
54
|
+
return
|
|
55
|
+
try:
|
|
56
|
+
data = tomllib.loads(pyproject.read_text(encoding="utf-8"))
|
|
57
|
+
except (tomllib.TOMLDecodeError, OSError, UnicodeDecodeError):
|
|
58
|
+
return
|
|
59
|
+
project = data.get("project")
|
|
60
|
+
project = project if isinstance(project, dict) else {}
|
|
61
|
+
raw_deps = project.get("dependencies")
|
|
62
|
+
specs = [s for s in raw_deps if isinstance(s, str)] if isinstance(raw_deps, list) else []
|
|
63
|
+
optional = project.get("optional-dependencies")
|
|
64
|
+
optional_specs: list[str] = []
|
|
65
|
+
if isinstance(optional, dict):
|
|
66
|
+
for group in optional.values():
|
|
67
|
+
if isinstance(group, list):
|
|
68
|
+
optional_specs.extend(s for s in group if isinstance(s, str))
|
|
69
|
+
if (specs or optional_specs) and "pip" not in pack.package_managers:
|
|
70
|
+
pack.package_managers.append("pip")
|
|
71
|
+
for spec in specs:
|
|
72
|
+
pack.dependencies.append({"name": spec, "version": "", "ecosystem": "pip"})
|
|
73
|
+
for spec in optional_specs:
|
|
74
|
+
pack.dependencies.append({"name": spec, "version": "", "ecosystem": "pip", "dev": True})
|
|
75
|
+
tool = data.get("tool")
|
|
76
|
+
has_pytest_config = isinstance(tool, dict) and isinstance(tool.get("pytest"), dict)
|
|
77
|
+
mentions_pytest = any(s.lower().startswith("pytest") for s in specs + optional_specs)
|
|
78
|
+
if (has_pytest_config or mentions_pytest) and "python -m pytest" not in pack.test_commands:
|
|
79
|
+
pack.test_commands.append("python -m pytest")
|
|
80
|
+
|
|
81
|
+
|
|
42
82
|
def build_context_pack(repo_path: Path) -> ProjectContextPack:
|
|
43
83
|
repo_path = Path(repo_path).resolve()
|
|
44
84
|
baseline = scan_repo_baseline(repo_path)
|
|
@@ -68,6 +108,8 @@ def build_context_pack(repo_path: Path) -> ProjectContextPack:
|
|
|
68
108
|
if not pack.test_commands:
|
|
69
109
|
pack.test_commands.append("python -m pytest")
|
|
70
110
|
|
|
111
|
+
_append_pyproject_facts(pack, repo_path)
|
|
112
|
+
|
|
71
113
|
for root, dirs, files in os.walk(repo_path):
|
|
72
114
|
dirs[:] = [d for d in dirs if d not in SKIP_DIRS and not d.startswith(".")]
|
|
73
115
|
rel_root = Path(root).relative_to(repo_path)
|
|
@@ -431,13 +431,14 @@ def _check_plan_context_pack(run_dir: Path) -> list[str]:
|
|
|
431
431
|
|
|
432
432
|
def _check_plan_file_refs(run_dir: Path, content: str) -> list[str]:
|
|
433
433
|
"""Hard-check Files paths against the Context Pack repo_root. create-type tasks
|
|
434
|
-
|
|
434
|
+
must target paths that do not exist yet (R19); the part after '::' (a symbol) is
|
|
435
|
+
advisory and not checked (no AST pack yet)."""
|
|
435
436
|
repo_root = _context_pack_repo_root(run_dir)
|
|
436
437
|
if repo_root is None:
|
|
437
438
|
return [] # no usable ground truth; standard tier blocks via _check_plan_context_pack
|
|
438
439
|
issues: list[str] = []
|
|
439
440
|
for body in _iter_plan_task_bodies(content):
|
|
440
|
-
|
|
441
|
+
is_create = _normalized_change_type(_task_change_type(body)) == "create"
|
|
441
442
|
files_field = _plan_task_field_body(body, "Files")
|
|
442
443
|
for path_part in _plan_task_file_paths(files_field):
|
|
443
444
|
path = Path(path_part)
|
|
@@ -453,11 +454,18 @@ def _check_plan_file_refs(run_dir: Path, content: str) -> list[str]:
|
|
|
453
454
|
)
|
|
454
455
|
continue
|
|
455
456
|
if not resolved.exists():
|
|
456
|
-
if not
|
|
457
|
+
if not is_create:
|
|
457
458
|
issues.append(
|
|
458
459
|
f"PLAN-TASK Files references missing path {path_part!r} "
|
|
459
460
|
"(mark the task 'Change Type: create' if it is a new file)."
|
|
460
461
|
)
|
|
462
|
+
elif is_create:
|
|
463
|
+
# R19: create must not silently mean "overwrite an existing file".
|
|
464
|
+
issues.append(
|
|
465
|
+
f"PLAN-TASK Files references path {path_part!r} that already exists "
|
|
466
|
+
"under 'Change Type: create'; use 'modify' for existing files or "
|
|
467
|
+
"split the task."
|
|
468
|
+
)
|
|
461
469
|
return issues
|
|
462
470
|
|
|
463
471
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
R2P_VERSION = "0.4.
|
|
1
|
+
R2P_VERSION = "0.4.3"
|