prizmkit 1.1.123 → 1.1.125

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 (26) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/adapters/claude/command-adapter.js +75 -0
  3. package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +16 -5
  4. package/bundled/dev-pipeline/prizmkit_runtime/runner_bookkeeping.py +121 -9
  5. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +3 -4
  6. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +1 -1
  7. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +4 -3
  8. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +4 -3
  9. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +7 -2
  10. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +5 -4
  11. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +4 -4
  12. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +5 -4
  13. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +6 -5
  14. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +2 -1
  15. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +197 -9
  16. package/bundled/dev-pipeline/tests/test_unified_cli.py +31 -16
  17. package/bundled/skills/_metadata.json +4 -4
  18. package/bundled/skills/prizmkit-code-review/SKILL.md +113 -110
  19. package/bundled/skills/prizmkit-code-review/assets/reviewer-role.json +19 -0
  20. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +24 -21
  21. package/bundled/skills/prizmkit-code-review/references/reviewer-agent-prompt.md +95 -67
  22. package/bundled/skills/prizmkit-code-review/scripts/render_review_report.py +162 -0
  23. package/package.json +1 -1
  24. package/src/scaffold.js +15 -0
  25. package/bundled/skills/prizmkit-code-review/references/reviewer-workspace-protocol.md +0 -90
  26. package/bundled/skills/prizmkit-code-review/scripts/check_loop.py +0 -186
@@ -1835,7 +1835,180 @@ def test_preflight_ready_commit_preserves_staged_unstaged_and_mixed_visible_chan
1835
1835
  assert visible_status == ""
1836
1836
 
1837
1837
 
1838
- def test_preflight_ready_commit_excludes_hidden_support_assets_and_state(tmp_path):
1838
+ def test_preflight_ready_commit_handles_exact_special_and_renamed_paths(tmp_path):
1839
+ from prizmkit_runtime.runner_bookkeeping import commit_preflight_ready_changes
1840
+
1841
+ _init_bookkeeping_repo(tmp_path)
1842
+ old_path = tmp_path / "old name -> café.txt"
1843
+ old_path.write_text("old\n", encoding="utf-8")
1844
+ subprocess.run(["git", "add", old_path.name], cwd=tmp_path, check=True)
1845
+ subprocess.run(["git", "commit", "-m", "track special path"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
1846
+ new_path = tmp_path / 'new "quoted" \\ 名称.txt'
1847
+ old_path.rename(new_path)
1848
+ untracked_path = tmp_path / "untracked -> line\nbreak.txt"
1849
+ untracked_path.write_text("new\n", encoding="utf-8")
1850
+ colon_path = tmp_path / ":(glob)**.txt"
1851
+ colon_path.write_text("literal pathspec\n", encoding="utf-8")
1852
+
1853
+ result = commit_preflight_ready_changes(tmp_path, "R-001")
1854
+ committed_files = set(
1855
+ filter(
1856
+ None,
1857
+ subprocess.run(
1858
+ ["git", "diff-tree", "--no-commit-id", "--name-only", "-r", "-z", "HEAD"],
1859
+ cwd=tmp_path,
1860
+ check=True,
1861
+ stdout=subprocess.PIPE,
1862
+ ).stdout.decode().split("\0"),
1863
+ )
1864
+ )
1865
+
1866
+ assert result.committed is True
1867
+ assert old_path.name in committed_files
1868
+ assert new_path.name in committed_files
1869
+ assert untracked_path.name in committed_files
1870
+ assert colon_path.name in committed_files
1871
+ assert _head_message(tmp_path) == "chore: ready for R-001"
1872
+
1873
+
1874
+ @pytest.mark.parametrize("staged", [False, True])
1875
+ def test_preflight_ready_commit_rejects_tracked_local_settings_before_checkout(tmp_path, staged):
1876
+ from prizmkit_runtime.runner_bookkeeping import commit_preflight_ready_changes
1877
+
1878
+ _init_bookkeeping_repo(tmp_path)
1879
+ local_settings = tmp_path / ".claude" / "settings.local.json"
1880
+ local_settings.parent.mkdir(parents=True)
1881
+ local_settings.write_text('{"permissions": []}\n', encoding="utf-8")
1882
+ subprocess.run(["git", "add", "-f", ".claude/settings.local.json"], cwd=tmp_path, check=True)
1883
+ subprocess.run(["git", "commit", "-m", "track local settings"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
1884
+ local_settings.write_text('{"permissions": ["secret"]}\n', encoding="utf-8")
1885
+ if staged:
1886
+ subprocess.run(["git", "add", ".claude/settings.local.json"], cwd=tmp_path, check=True)
1887
+
1888
+ result = commit_preflight_ready_changes(tmp_path, "B-001")
1889
+
1890
+ assert result.attempted is True
1891
+ assert result.committed is False
1892
+ assert result.result is not None
1893
+ assert "Refusing to auto-commit local-only platform settings" in result.result.stderr
1894
+ assert _head_message(tmp_path) == "track local settings"
1895
+
1896
+
1897
+ def test_preflight_ready_commit_excludes_untracked_python_cache_outside_support_dirs(tmp_path):
1898
+ from prizmkit_runtime.runner_bookkeeping import commit_preflight_ready_changes
1899
+
1900
+ _init_bookkeeping_repo(tmp_path)
1901
+ (tmp_path / "visible.txt").write_text("visible\n", encoding="utf-8")
1902
+ cache_paths = [
1903
+ tmp_path / "__pycache__" / "top.cpython-312.pyc",
1904
+ tmp_path / "src" / "package" / "__pycache__" / "module.cpython-312.pyc",
1905
+ tmp_path / "src" / "standalone.pyo",
1906
+ ]
1907
+ for path in cache_paths:
1908
+ path.parent.mkdir(parents=True, exist_ok=True)
1909
+ path.write_text("compiled\n", encoding="utf-8")
1910
+
1911
+ result = commit_preflight_ready_changes(tmp_path, "F-001")
1912
+ committed_files = _head_files(tmp_path)
1913
+
1914
+ assert result.committed is True
1915
+ assert committed_files == {"visible.txt"}
1916
+ assert all(path.is_file() for path in cache_paths)
1917
+
1918
+
1919
+ def test_tracked_python_cache_remains_visible_to_task_commit(tmp_path):
1920
+ from prizmkit_runtime.runner_bookkeeping import commit_task_branch_changes
1921
+ from prizmkit_runtime.runner_models import family_for
1922
+
1923
+ paths = _make_paths(tmp_path)
1924
+ _init_bookkeeping_repo(paths.project_root)
1925
+ tracked_cache = paths.project_root / "src" / "__pycache__" / "module.pyc"
1926
+ tracked_cache.parent.mkdir(parents=True)
1927
+ tracked_cache.write_text("baseline\n", encoding="utf-8")
1928
+ subprocess.run(["git", "add", tracked_cache.relative_to(paths.project_root).as_posix()], cwd=paths.project_root, check=True)
1929
+ subprocess.run(["git", "commit", "-m", "track cache"], cwd=paths.project_root, check=True, stdout=subprocess.DEVNULL)
1930
+ tracked_cache.write_text("task change\n", encoding="utf-8")
1931
+
1932
+ result = commit_task_branch_changes(paths.project_root, family_for("bugfix", paths), "B-001", "completed")
1933
+
1934
+ assert result.committed is True
1935
+ assert tracked_cache.relative_to(paths.project_root).as_posix() in _head_files(paths.project_root)
1936
+ assert subprocess.run(
1937
+ ["git", "status", "--short", "--untracked-files=no"],
1938
+ cwd=paths.project_root,
1939
+ check=True,
1940
+ stdout=subprocess.PIPE,
1941
+ text=True,
1942
+ ).stdout == ""
1943
+
1944
+
1945
+ def test_preflight_ready_commit_includes_tracked_state_but_excludes_untracked_state(tmp_path):
1946
+ from prizmkit_runtime.runner_bookkeeping import commit_preflight_ready_changes
1947
+
1948
+ _init_bookkeeping_repo(tmp_path)
1949
+ tracked_state = tmp_path / ".prizmkit" / "state" / "bugfix" / "B-001" / "tracked.json"
1950
+ tracked_state.parent.mkdir(parents=True)
1951
+ tracked_state.write_text("baseline\n", encoding="utf-8")
1952
+ subprocess.run(["git", "add", "-f", tracked_state.relative_to(tmp_path).as_posix()], cwd=tmp_path, check=True)
1953
+ subprocess.run(["git", "commit", "-m", "track state"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
1954
+ tracked_state.write_text("changed\n", encoding="utf-8")
1955
+ untracked_state = tracked_state.parent / "untracked.json"
1956
+ untracked_state.write_text("transient\n", encoding="utf-8")
1957
+
1958
+ result = commit_preflight_ready_changes(tmp_path, "B-001")
1959
+
1960
+ assert result.committed is True
1961
+ assert tracked_state.relative_to(tmp_path).as_posix() in _head_files(tmp_path)
1962
+ assert untracked_state.relative_to(tmp_path).as_posix() not in _head_files(tmp_path)
1963
+
1964
+
1965
+ def test_preflight_ready_commit_includes_tracked_framework_support_but_excludes_untracked_support_and_state(tmp_path):
1966
+ from prizmkit_runtime.runner_bookkeeping import commit_preflight_ready_changes
1967
+
1968
+ _init_bookkeeping_repo(tmp_path)
1969
+ tracked_support_paths = [
1970
+ tmp_path / ".prizmkit" / "manifest.json",
1971
+ tmp_path / ".prizmkit" / "dev-pipeline" / "prizmkit_runtime" / "runners.py",
1972
+ tmp_path / ".claude" / "commands" / "prizmkit.md",
1973
+ ]
1974
+ for path in tracked_support_paths:
1975
+ path.parent.mkdir(parents=True, exist_ok=True)
1976
+ path.write_text("installed\n", encoding="utf-8")
1977
+ subprocess.run(
1978
+ ["git", "add", *(path.relative_to(tmp_path).as_posix() for path in tracked_support_paths)],
1979
+ cwd=tmp_path,
1980
+ check=True,
1981
+ )
1982
+ subprocess.run(["git", "commit", "-m", "track support"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
1983
+ for path in tracked_support_paths:
1984
+ path.write_text("upgraded\n", encoding="utf-8")
1985
+
1986
+ untracked_support_paths = [
1987
+ tmp_path / ".claude" / "settings.local.json",
1988
+ tmp_path / ".prizmkit" / "dev-pipeline" / "scripts" / "__pycache__" / "runtime.pyc",
1989
+ tmp_path / ".prizmkit" / "state" / "bugfix" / "B-001" / "status.json",
1990
+ ]
1991
+ for path in untracked_support_paths:
1992
+ path.parent.mkdir(parents=True, exist_ok=True)
1993
+ path.write_text("transient\n", encoding="utf-8")
1994
+
1995
+ result = commit_preflight_ready_changes(tmp_path, "B-001")
1996
+ committed_files = _head_files(tmp_path)
1997
+
1998
+ assert result.committed is True
1999
+ assert {path.relative_to(tmp_path).as_posix() for path in tracked_support_paths}.issubset(committed_files)
2000
+ assert not {path.relative_to(tmp_path).as_posix() for path in untracked_support_paths}.intersection(committed_files)
2001
+ assert _head_message(tmp_path) == "chore: ready for B-001"
2002
+ assert subprocess.run(
2003
+ ["git", "status", "--short", "--untracked-files=no"],
2004
+ cwd=tmp_path,
2005
+ check=True,
2006
+ stdout=subprocess.PIPE,
2007
+ text=True,
2008
+ ).stdout == ""
2009
+
2010
+
2011
+ def test_preflight_ready_commit_excludes_untracked_support_assets_and_state(tmp_path):
1839
2012
  from prizmkit_runtime.runner_bookkeeping import commit_preflight_ready_changes
1840
2013
 
1841
2014
  _init_bookkeeping_repo(tmp_path)
@@ -1882,23 +2055,38 @@ def test_preflight_ready_commit_noops_when_workspace_has_no_visible_dirty_conten
1882
2055
  assert _head_commit_count(tmp_path) == before_count
1883
2056
 
1884
2057
 
1885
- def test_process_item_creates_one_ready_commit_before_branch_setup_for_mixed_visible_changes(monkeypatch, tmp_path):
2058
+ @pytest.mark.parametrize(
2059
+ ("kind", "item_id", "branch"),
2060
+ [
2061
+ ("feature", "F-001", "dev/F-001-ready"),
2062
+ ("bugfix", "B-001", "bugfix/B-001-ready"),
2063
+ ("refactor", "R-001", "refactor/R-001-ready"),
2064
+ ],
2065
+ )
2066
+ def test_process_item_creates_one_ready_commit_before_branch_setup_for_all_families(
2067
+ monkeypatch,
2068
+ tmp_path,
2069
+ kind,
2070
+ item_id,
2071
+ branch,
2072
+ ):
1886
2073
  from prizmkit_runtime import runner_bookkeeping, runners
1887
2074
  from prizmkit_runtime.gitops import HIDDEN_TOOL_WORKTREE_EXCLUDES
1888
2075
  from prizmkit_runtime.runner_models import family_for, parse_invocation
1889
2076
 
1890
- paths = _make_paths(tmp_path)
2077
+ paths = _make_paths(tmp_path / kind)
1891
2078
  _init_bookkeeping_repo(paths.project_root)
1892
2079
  (paths.project_root / "staged.txt").write_text("staged before run\n", encoding="utf-8")
1893
2080
  subprocess.run(["git", "add", "staged.txt"], cwd=paths.project_root, check=True)
1894
2081
  (paths.project_root / "unstaged.txt").write_text("unstaged before run\n", encoding="utf-8")
2082
+ (paths.project_root / "untracked.txt").write_text("untracked before run\n", encoding="utf-8")
1895
2083
  before_count = _head_commit_count(paths.project_root)
1896
2084
  branch_events = []
1897
2085
 
1898
- monkeypatch.setenv("DEV_BRANCH", "dev/F-001-ready")
1899
- family = family_for("feature", paths)
2086
+ monkeypatch.setenv("DEV_BRANCH", branch)
2087
+ family = family_for(kind, paths)
1900
2088
  invocation = parse_invocation(family, "run", ())
1901
- invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
2089
+ invocation = invocation.__class__(**{**invocation.__dict__, "list_path": family.plan_path})
1902
2090
  _install_runner_session_fakes(monkeypatch, runners)
1903
2091
  monkeypatch.setattr(runners, "commit_preflight_ready_changes", runner_bookkeeping.commit_preflight_ready_changes)
1904
2092
 
@@ -1921,18 +2109,18 @@ def test_process_item_creates_one_ready_commit_before_branch_setup_for_mixed_vis
1921
2109
 
1922
2110
  monkeypatch.setattr(runners, "run_git_plan", fake_run_git_plan)
1923
2111
 
1924
- status = runners._process_item(family, invocation, "F-001", paths, initial_metadata={})
2112
+ status = runners._process_item(family, invocation, item_id, paths, initial_metadata={})
1925
2113
 
1926
2114
  assert status == "completed"
1927
2115
  assert branch_events == [
1928
2116
  {
1929
2117
  "commit_count": before_count + 1,
1930
- "message": "chore: ready for F-001",
2118
+ "message": f"chore: ready for {item_id}",
1931
2119
  "visible_status": "",
1932
2120
  }
1933
2121
  ]
1934
2122
  assert _head_commit_count(paths.project_root) == before_count + 1
1935
- assert {"staged.txt", "unstaged.txt"}.issubset(_head_files(paths.project_root))
2123
+ assert {"staged.txt", "unstaged.txt", "untracked.txt"}.issubset(_head_files(paths.project_root))
1936
2124
 
1937
2125
 
1938
2126
 
@@ -811,33 +811,48 @@ def test_session_preamble_writes_redacted_start_command_for_all_launch_profiles(
811
811
  assert str(prompt) not in "\n".join(lines[:6])
812
812
 
813
813
 
814
- def test_headless_review_guidance_uses_workspace_equivalence_protocol():
815
- guidance_files = [
814
+ def test_headless_review_guidance_uses_minimal_reviewer_loop():
815
+ headless_guidance_files = [
816
816
  REPO_ROOT / "dev-pipeline" / "templates" / "sections" / "phase-review-full.md",
817
817
  REPO_ROOT / "dev-pipeline" / "templates" / "bootstrap-tier3.md",
818
- REPO_ROOT / "core" / "skills" / "prizmkit-skill" / "prizmkit-code-review" / "SKILL.md",
819
818
  ]
819
+ skill_path = (
820
+ REPO_ROOT
821
+ / "core"
822
+ / "skills"
823
+ / "prizmkit-skill"
824
+ / "prizmkit-code-review"
825
+ / "SKILL.md"
826
+ )
820
827
 
821
- for path in guidance_files:
828
+ for path in headless_guidance_files:
822
829
  text = path.read_text(encoding="utf-8")
823
830
  assert "platform-supported" in text, path
824
- assert "snapshot" in text and "equivalent" in text, path
831
+ assert "active checkout" in text, path
832
+ assert "platform-forced worktree" in text, path
825
833
  assert "Main-Agent self-review" in text, path
834
+ assert "workspace-equivalent" not in text, path
835
+ assert "workspace protocol" not in text, path
826
836
  assert "current-workspace / active-checkout / no-worktree" not in text, path
827
837
  assert ".claude/worktrees/..." not in text, path
828
838
  assert ".prizmkit/state/worktrees/..." not in text, path
829
839
  assert "USE_WORKTREE" not in text, path
830
840
  assert "isolation:" not in text, path
831
-
832
- skill_text = guidance_files[-1].read_text(encoding="utf-8")
833
- assert "reviewer-workspace-protocol.md" in skill_text
834
- assert "staged, unstaged, untracked, deleted, and renamed" in skill_text
835
- assert "WORKSPACE_MISMATCH" in skill_text
836
-
837
- for path in guidance_files[:-1]:
838
- text = path.read_text(encoding="utf-8")
839
841
  assert "Read existing source files in the modules this plan touches" not in text, path
840
842
 
843
+ skill_text = skill_path.read_text(encoding="utf-8")
844
+ assert "staged and unstaged tracked changes" in skill_text
845
+ assert "untracked files" in skill_text
846
+ assert "accepted = 0" in skill_text
847
+ assert "all findings are rejected" in skill_text
848
+ assert "BLOCKED" in skill_text
849
+ assert "Do not substitute Main-Agent self-review" in skill_text
850
+ assert "platform forces an isolated worktree" in skill_text
851
+ assert "reviewer-workspace-protocol.md" not in skill_text
852
+ assert "reviewer-execution-protocol.md" not in skill_text
853
+ assert "WORKSPACE_MISMATCH" not in skill_text
854
+ assert "REVIEW_INFRASTRUCTURE_BLOCKED" not in skill_text
855
+
841
856
 
842
857
  def test_progress_parser_adds_required_metadata(tmp_path):
843
858
  import importlib.util
@@ -2109,7 +2124,7 @@ def test_preflight_ready_commit_skips_hidden_support_asset_only_changes(tmp_path
2109
2124
  assert (repo / ".claude" / "settings.local.json").is_file()
2110
2125
 
2111
2126
 
2112
- def test_preflight_ready_commit_excludes_already_staged_hidden_support_assets(tmp_path):
2127
+ def test_preflight_ready_commit_includes_explicitly_staged_support_assets(tmp_path):
2113
2128
  from prizmkit_runtime.runner_bookkeeping import commit_preflight_ready_changes
2114
2129
 
2115
2130
  repo = tmp_path / "repo"
@@ -2123,8 +2138,8 @@ def test_preflight_ready_commit_excludes_already_staged_hidden_support_assets(tm
2123
2138
 
2124
2139
  assert result.committed
2125
2140
  assert _head_commit_message(repo) == "chore: ready for F-005"
2126
- assert _head_files(repo) == {"visible.txt"}
2127
- assert "A .claude/agent.md" in _short_status(repo)
2141
+ assert _head_files(repo) == {".claude/agent.md", "visible.txt"}
2142
+ assert _short_status(repo) == ""
2128
2143
 
2129
2144
 
2130
2145
  def test_preflight_ready_commit_excludes_ignored_tool_worktree_directories(tmp_path):
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1.123",
2
+ "version": "1.1.125",
3
3
  "skills": {
4
4
  "prizmkit": {
5
5
  "description": "Full-lifecycle dev toolkit. Covers spec-driven development, Prizm context docs, code quality, debugging, deployment, and knowledge management.",
@@ -30,11 +30,11 @@
30
30
  "hasScripts": false
31
31
  },
32
32
  "prizmkit-code-review": {
33
- "description": "Code review with fix strategy formulation. Diagnoses issues, then produces structured Fix Instructions with root cause, impact analysis, and verification criteria.",
33
+ "description": "Independent read-only review with Main-Agent finding adjudication, direct repairs, bounded convergence, and a minimal final report.",
34
34
  "tier": "1",
35
35
  "category": "prizmkit-skill",
36
- "hasAssets": false,
37
- "hasScripts": false
36
+ "hasAssets": true,
37
+ "hasScripts": true
38
38
  },
39
39
  "prizmkit-committer": {
40
40
  "description": "Pure git commit workflow with safety checks. Does NOT modify .prizmkit/prizm-docs/.",