prizmkit 1.1.116 → 1.1.118
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/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +48 -2
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +82 -4
- package/bundled/dev-pipeline/tests/test_unified_cli.py +47 -4
- package/bundled/skills/_metadata.json +1 -1
- package/package.json +1 -1
package/bundled/VERSION.json
CHANGED
|
@@ -39,6 +39,9 @@ HIDDEN_TOOL_WORKTREE_EXCLUDES = (
|
|
|
39
39
|
":(top,exclude,glob).prizmkit/dev-pipeline/prizmkit_runtime/**",
|
|
40
40
|
)
|
|
41
41
|
|
|
42
|
+
PIPELINE_FALLBACK_GIT_NAME = "PrizmKit Pipeline"
|
|
43
|
+
PIPELINE_FALLBACK_GIT_EMAIL = "prizmkit-pipeline@example.invalid"
|
|
44
|
+
|
|
42
45
|
WORKTREE_PLATFORM_SUPPORT_DIRS = (".claude", ".codebuddy", ".codex", ".agents")
|
|
43
46
|
WORKTREE_ROOT_SUPPORT_FILES = ("skills-lock.json", "AGENTS.md")
|
|
44
47
|
WORKTREE_PRIZMKIT_SUPPORT_ENTRIES = ("prizm-docs", "config.json", "manifest.json")
|
|
@@ -640,17 +643,60 @@ def branch_ensure_return(project_root: Path, base_branch: str, working_branch: s
|
|
|
640
643
|
context = BranchContext(base_branch=base_branch, working_branch=working_branch or active, project_root=project_root)
|
|
641
644
|
return run_git_plan(project_root, branch_ensure_return_plan(context))
|
|
642
645
|
|
|
646
|
+
|
|
647
|
+
def _git_config_has_value(project_root: Path, key: str) -> bool:
|
|
648
|
+
completed = subprocess.run(
|
|
649
|
+
["git", "-C", str(project_root), "config", "--get", key],
|
|
650
|
+
stdout=subprocess.PIPE,
|
|
651
|
+
stderr=subprocess.PIPE,
|
|
652
|
+
text=True,
|
|
653
|
+
check=False,
|
|
654
|
+
)
|
|
655
|
+
return completed.returncode == 0 and bool(completed.stdout.strip())
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def _identity_value_available(project_root: Path, config_key: str, author_env: str, committer_env: str) -> bool:
|
|
659
|
+
return _git_config_has_value(project_root, config_key) or bool(
|
|
660
|
+
os.environ.get(author_env) and os.environ.get(committer_env)
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
def _commit_identity_global_args(project_root: Path) -> tuple[str, ...]:
|
|
665
|
+
global_args: list[str] = []
|
|
666
|
+
if not _identity_value_available(project_root, "user.name", "GIT_AUTHOR_NAME", "GIT_COMMITTER_NAME"):
|
|
667
|
+
global_args.extend(("-c", f"user.name={PIPELINE_FALLBACK_GIT_NAME}"))
|
|
668
|
+
if not _identity_value_available(project_root, "user.email", "GIT_AUTHOR_EMAIL", "GIT_COMMITTER_EMAIL"):
|
|
669
|
+
global_args.extend(("-c", f"user.email={PIPELINE_FALLBACK_GIT_EMAIL}"))
|
|
670
|
+
return tuple(global_args)
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
def _commit_args_without_signing(args: tuple[str, ...]) -> tuple[str, ...]:
|
|
674
|
+
explicit_signing = any(
|
|
675
|
+
arg == "-S" or arg.startswith("-S") or arg == "--gpg-sign" or arg.startswith("--gpg-sign=")
|
|
676
|
+
for arg in args[1:]
|
|
677
|
+
)
|
|
678
|
+
if explicit_signing or "--no-gpg-sign" in args:
|
|
679
|
+
return args
|
|
680
|
+
return ("commit", "--no-gpg-sign", *args[1:])
|
|
681
|
+
|
|
682
|
+
|
|
643
683
|
def run_git_command(project_root: Path, args: Sequence[str]) -> GitCommandResult:
|
|
644
684
|
"""Run one git command in a project root and capture output."""
|
|
685
|
+
command_args = tuple(map(str, args))
|
|
686
|
+
global_args: tuple[str, ...] = ()
|
|
687
|
+
executable_args = command_args
|
|
688
|
+
if command_args and command_args[0] == "commit":
|
|
689
|
+
global_args = _commit_identity_global_args(project_root)
|
|
690
|
+
executable_args = _commit_args_without_signing(command_args)
|
|
645
691
|
completed = subprocess.run(
|
|
646
|
-
["git", "-C", str(project_root), *
|
|
692
|
+
["git", *global_args, "-C", str(project_root), *executable_args],
|
|
647
693
|
stdout=subprocess.PIPE,
|
|
648
694
|
stderr=subprocess.PIPE,
|
|
649
695
|
text=True,
|
|
650
696
|
check=False,
|
|
651
697
|
)
|
|
652
698
|
return GitCommandResult(
|
|
653
|
-
args=
|
|
699
|
+
args=command_args,
|
|
654
700
|
return_code=completed.returncode,
|
|
655
701
|
stdout=completed.stdout,
|
|
656
702
|
stderr=completed.stderr,
|
|
@@ -1570,14 +1570,29 @@ def test_no_worktree_interrupt_saves_wip_and_returns_to_base(monkeypatch, tmp_pa
|
|
|
1570
1570
|
assert ensure_calls == [("main", "dev/F-001-interrupt")]
|
|
1571
1571
|
|
|
1572
1572
|
|
|
1573
|
-
def _init_bookkeeping_repo(project_root):
|
|
1573
|
+
def _init_bookkeeping_repo(project_root, *, configure_identity=True):
|
|
1574
1574
|
subprocess.run(["git", "init", "-b", "main"], cwd=project_root, check=True, stdout=subprocess.DEVNULL)
|
|
1575
|
-
|
|
1576
|
-
|
|
1575
|
+
if configure_identity:
|
|
1576
|
+
subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=project_root, check=True)
|
|
1577
|
+
subprocess.run(["git", "config", "user.name", "Test"], cwd=project_root, check=True)
|
|
1577
1578
|
(project_root / "staged.txt").write_text("base staged\n", encoding="utf-8")
|
|
1578
1579
|
(project_root / "unstaged.txt").write_text("base unstaged\n", encoding="utf-8")
|
|
1579
1580
|
subprocess.run(["git", "add", "staged.txt", "unstaged.txt"], cwd=project_root, check=True)
|
|
1580
|
-
subprocess.run(
|
|
1581
|
+
subprocess.run(
|
|
1582
|
+
[
|
|
1583
|
+
"git",
|
|
1584
|
+
"-c",
|
|
1585
|
+
"user.email=test@example.com",
|
|
1586
|
+
"-c",
|
|
1587
|
+
"user.name=Test",
|
|
1588
|
+
"commit",
|
|
1589
|
+
"-m",
|
|
1590
|
+
"base",
|
|
1591
|
+
],
|
|
1592
|
+
cwd=project_root,
|
|
1593
|
+
check=True,
|
|
1594
|
+
stdout=subprocess.DEVNULL,
|
|
1595
|
+
)
|
|
1581
1596
|
|
|
1582
1597
|
|
|
1583
1598
|
def _head_commit_count(project_root):
|
|
@@ -1754,6 +1769,69 @@ def test_process_item_creates_one_ready_commit_before_branch_setup_for_mixed_vis
|
|
|
1754
1769
|
|
|
1755
1770
|
|
|
1756
1771
|
|
|
1772
|
+
def test_process_item_ready_commit_uses_fallback_identity_before_branch_setup(monkeypatch, tmp_path):
|
|
1773
|
+
from prizmkit_runtime import runner_bookkeeping, runners
|
|
1774
|
+
from prizmkit_runtime.gitops import HIDDEN_TOOL_WORKTREE_EXCLUDES, PIPELINE_FALLBACK_GIT_EMAIL, PIPELINE_FALLBACK_GIT_NAME
|
|
1775
|
+
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
1776
|
+
|
|
1777
|
+
monkeypatch.setenv("GIT_CONFIG_GLOBAL", str(tmp_path / "missing-global-gitconfig"))
|
|
1778
|
+
monkeypatch.setenv("GIT_CONFIG_NOSYSTEM", "1")
|
|
1779
|
+
for key in ("GIT_AUTHOR_NAME", "GIT_AUTHOR_EMAIL", "GIT_COMMITTER_NAME", "GIT_COMMITTER_EMAIL"):
|
|
1780
|
+
monkeypatch.delenv(key, raising=False)
|
|
1781
|
+
paths = _make_paths(tmp_path)
|
|
1782
|
+
_init_bookkeeping_repo(paths.project_root, configure_identity=False)
|
|
1783
|
+
(paths.project_root / "new-visible.txt").write_text("dirty before run\n", encoding="utf-8")
|
|
1784
|
+
before_count = _head_commit_count(paths.project_root)
|
|
1785
|
+
branch_events = []
|
|
1786
|
+
|
|
1787
|
+
monkeypatch.setenv("DEV_BRANCH", "dev/F-001-ready-no-identity")
|
|
1788
|
+
family = family_for("feature", paths)
|
|
1789
|
+
invocation = parse_invocation(family, "run", ())
|
|
1790
|
+
invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
|
|
1791
|
+
_install_runner_session_fakes(monkeypatch, runners)
|
|
1792
|
+
monkeypatch.setattr(runners, "commit_preflight_ready_changes", runner_bookkeeping.commit_preflight_ready_changes)
|
|
1793
|
+
|
|
1794
|
+
def fake_run_git_plan(project_root, plan):
|
|
1795
|
+
if plan.name == "branch_create":
|
|
1796
|
+
author = subprocess.run(
|
|
1797
|
+
["git", "log", "-1", "--format=%an <%ae>"],
|
|
1798
|
+
cwd=project_root,
|
|
1799
|
+
check=True,
|
|
1800
|
+
stdout=subprocess.PIPE,
|
|
1801
|
+
text=True,
|
|
1802
|
+
).stdout.strip()
|
|
1803
|
+
branch_events.append(
|
|
1804
|
+
{
|
|
1805
|
+
"commit_count": _head_commit_count(project_root),
|
|
1806
|
+
"message": _head_message(project_root),
|
|
1807
|
+
"author": author,
|
|
1808
|
+
"visible_status": subprocess.run(
|
|
1809
|
+
["git", "status", "--porcelain", "--", ".", *HIDDEN_TOOL_WORKTREE_EXCLUDES],
|
|
1810
|
+
cwd=project_root,
|
|
1811
|
+
check=True,
|
|
1812
|
+
stdout=subprocess.PIPE,
|
|
1813
|
+
text=True,
|
|
1814
|
+
).stdout,
|
|
1815
|
+
}
|
|
1816
|
+
)
|
|
1817
|
+
return SimpleNamespace(ok=True, plan=plan, results=())
|
|
1818
|
+
|
|
1819
|
+
monkeypatch.setattr(runners, "run_git_plan", fake_run_git_plan)
|
|
1820
|
+
|
|
1821
|
+
status = runners._process_item(family, invocation, "F-001", paths, initial_metadata={})
|
|
1822
|
+
|
|
1823
|
+
assert status == "completed"
|
|
1824
|
+
assert branch_events == [
|
|
1825
|
+
{
|
|
1826
|
+
"commit_count": before_count + 1,
|
|
1827
|
+
"message": "chore: ready for F-001",
|
|
1828
|
+
"author": f"{PIPELINE_FALLBACK_GIT_NAME} <{PIPELINE_FALLBACK_GIT_EMAIL}>",
|
|
1829
|
+
"visible_status": "",
|
|
1830
|
+
}
|
|
1831
|
+
]
|
|
1832
|
+
assert "new-visible.txt" in _head_files(paths.project_root)
|
|
1833
|
+
|
|
1834
|
+
|
|
1757
1835
|
def test_bookkeeping_commits_only_runner_owned_paths(tmp_path):
|
|
1758
1836
|
from prizmkit_runtime.runner_bookkeeping import commit_final_bookkeeping
|
|
1759
1837
|
from prizmkit_runtime.runner_models import RunnerFamily
|
|
@@ -1776,14 +1776,29 @@ def test_git_status_safe_ignores_worktree_support_assets(monkeypatch, tmp_path):
|
|
|
1776
1776
|
assert ".prizmkit-worktree" in joined
|
|
1777
1777
|
|
|
1778
1778
|
|
|
1779
|
-
def _init_temp_repo(repo):
|
|
1779
|
+
def _init_temp_repo(repo, *, configure_identity=True):
|
|
1780
1780
|
repo.mkdir(parents=True, exist_ok=True)
|
|
1781
1781
|
subprocess.run(["git", "init", "-b", "main"], cwd=repo, check=True, stdout=subprocess.DEVNULL)
|
|
1782
|
-
|
|
1783
|
-
|
|
1782
|
+
if configure_identity:
|
|
1783
|
+
subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=repo, check=True)
|
|
1784
|
+
subprocess.run(["git", "config", "user.name", "Test"], cwd=repo, check=True)
|
|
1784
1785
|
(repo / "base.txt").write_text("base\n", encoding="utf-8")
|
|
1785
1786
|
subprocess.run(["git", "add", "base.txt"], cwd=repo, check=True)
|
|
1786
|
-
subprocess.run(
|
|
1787
|
+
subprocess.run(
|
|
1788
|
+
[
|
|
1789
|
+
"git",
|
|
1790
|
+
"-c",
|
|
1791
|
+
"user.email=test@example.com",
|
|
1792
|
+
"-c",
|
|
1793
|
+
"user.name=Test",
|
|
1794
|
+
"commit",
|
|
1795
|
+
"-m",
|
|
1796
|
+
"base",
|
|
1797
|
+
],
|
|
1798
|
+
cwd=repo,
|
|
1799
|
+
check=True,
|
|
1800
|
+
stdout=subprocess.DEVNULL,
|
|
1801
|
+
)
|
|
1787
1802
|
|
|
1788
1803
|
|
|
1789
1804
|
def _head_commit_message(repo):
|
|
@@ -1915,6 +1930,34 @@ def test_preflight_ready_commit_excludes_ignored_tool_worktree_directories(tmp_p
|
|
|
1915
1930
|
assert (repo / ".cache" / "worktrees" / "agent" / "artifact.txt").is_file()
|
|
1916
1931
|
|
|
1917
1932
|
|
|
1933
|
+
def test_preflight_ready_commit_preserves_dirty_changes_without_repo_git_identity(tmp_path, monkeypatch):
|
|
1934
|
+
from prizmkit_runtime.gitops import PIPELINE_FALLBACK_GIT_EMAIL, PIPELINE_FALLBACK_GIT_NAME
|
|
1935
|
+
from prizmkit_runtime.runner_bookkeeping import commit_preflight_ready_changes
|
|
1936
|
+
|
|
1937
|
+
repo = tmp_path / "repo"
|
|
1938
|
+
monkeypatch.setenv("GIT_CONFIG_GLOBAL", str(tmp_path / "missing-global-gitconfig"))
|
|
1939
|
+
monkeypatch.setenv("GIT_CONFIG_NOSYSTEM", "1")
|
|
1940
|
+
for key in ("GIT_AUTHOR_NAME", "GIT_AUTHOR_EMAIL", "GIT_COMMITTER_NAME", "GIT_COMMITTER_EMAIL"):
|
|
1941
|
+
monkeypatch.delenv(key, raising=False)
|
|
1942
|
+
_init_temp_repo(repo, configure_identity=False)
|
|
1943
|
+
(repo / "visible.txt").write_text("dirty\n", encoding="utf-8")
|
|
1944
|
+
|
|
1945
|
+
result = commit_preflight_ready_changes(repo, "F-007")
|
|
1946
|
+
author = subprocess.run(
|
|
1947
|
+
["git", "log", "-1", "--format=%an <%ae>"],
|
|
1948
|
+
cwd=repo,
|
|
1949
|
+
check=True,
|
|
1950
|
+
stdout=subprocess.PIPE,
|
|
1951
|
+
text=True,
|
|
1952
|
+
).stdout.strip()
|
|
1953
|
+
|
|
1954
|
+
assert result.committed
|
|
1955
|
+
assert _head_commit_message(repo) == "chore: ready for F-007"
|
|
1956
|
+
assert _head_files(repo) == {"visible.txt"}
|
|
1957
|
+
assert author == f"{PIPELINE_FALLBACK_GIT_NAME} <{PIPELINE_FALLBACK_GIT_EMAIL}>"
|
|
1958
|
+
assert _short_status(repo) == ""
|
|
1959
|
+
|
|
1960
|
+
|
|
1918
1961
|
def test_materialize_worktree_support_assets_copies_ignored_assets_without_retired_shell_sources(tmp_path):
|
|
1919
1962
|
from prizmkit_runtime.gitops import (
|
|
1920
1963
|
BranchContext,
|