prizmkit 1.1.128 → 1.1.130

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 (41) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +88 -58
  3. package/bundled/dev-pipeline/prizmkit_runtime/reset.py +4 -3
  4. package/bundled/dev-pipeline/prizmkit_runtime/runner_bookkeeping.py +53 -1
  5. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +33 -17
  6. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +30 -28
  7. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +8 -3
  8. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +3 -2
  9. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +11 -4
  10. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +3 -3
  11. package/bundled/dev-pipeline/scripts/prompt_framework.py +4 -6
  12. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +2 -2
  13. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +20 -19
  14. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +2 -2
  15. package/bundled/dev-pipeline/templates/sections/bugfix-mission.md +1 -1
  16. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +10 -2
  17. package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +1 -1
  18. package/bundled/dev-pipeline/templates/sections/directory-convention-agent.md +3 -2
  19. package/bundled/dev-pipeline/templates/sections/directory-convention-full.md +3 -2
  20. package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +1 -1
  21. package/bundled/dev-pipeline/templates/sections/phase-commit.md +1 -1
  22. package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +1 -1
  23. package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +1 -1
  24. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +11 -3
  25. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +11 -3
  26. package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +1 -1
  27. package/bundled/dev-pipeline/templates/sections/phase0-init.md +1 -1
  28. package/bundled/dev-pipeline/templates/sections/refactor-mission.md +1 -1
  29. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +11 -3
  30. package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +1 -1
  31. package/bundled/dev-pipeline/templates/sections/subagent-timeout-recovery.md +6 -4
  32. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-agent.md +1 -1
  33. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +192 -5
  34. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +139 -11
  35. package/bundled/dev-pipeline/tests/test_unified_cli.py +215 -9
  36. package/bundled/skills/_metadata.json +1 -1
  37. package/bundled/skills/feature-planner/SKILL.md +2 -2
  38. package/bundled/skills/prizmkit/SKILL.md +1 -1
  39. package/bundled/skills/prizmkit-code-review/SKILL.md +10 -1
  40. package/package.json +1 -1
  41. package/src/manifest.js +8 -3
@@ -2,6 +2,7 @@
2
2
 
3
3
  import json
4
4
  import os
5
+ import re
5
6
  import shutil
6
7
  import subprocess
7
8
  import sys
@@ -830,7 +831,8 @@ def test_headless_review_guidance_uses_main_agent_review_loop():
830
831
  assert "Main-Agent review loop" in text, path
831
832
  assert "up to ten completed rounds" in text, path
832
833
  assert "text final-verdict" in text, path
833
- assert "code-review-specific subagent" in text, path
834
+ assert "The current Main Agent is the only Code Review executor" in text, path
835
+ assert "Do not invoke another review skill or review workflow" in text, path
834
836
  assert "low=0" not in text and "Reviewer 3" not in text, path
835
837
  assert "REVIEW_BLOCKED" not in text, path
836
838
  assert "forced-worktree changed-content fallback" not in text, path
@@ -947,10 +949,11 @@ def test_progress_parser_normalizes_codebuddy_agent_tools_with_deduplication():
947
949
 
948
950
  assert data["cb_session_id"] == "cb-session"
949
951
  assert data["subagent_provider"] == "codebuddy"
950
- assert data["subagent_spawn_count"] == 2
951
- assert data["active_subagent_count"] == 2
952
+ assert data["subagent_spawn_count"] == 1
953
+ assert data["active_subagent_count"] == 1
952
954
  assert {item["status"] for item in data["subagent_states"]} == {"running"}
953
- assert len(data["subagent_statistics"]["records"]) == 2
955
+ assert len(data["subagent_statistics"]["records"]) == 1
956
+ assert data["subagent_statistics"]["records"][0]["metadata"]["tool"] == "Agent"
954
957
 
955
958
 
956
959
  def test_progress_parser_normalizes_codex_collab_metadata_with_missing_fields():
@@ -1827,14 +1830,18 @@ def test_git_branch_worktree_plans_preserve_shell_invariants(tmp_path):
1827
1830
 
1828
1831
  assert create.name == "branch_create"
1829
1832
  assert any(command.args[:2] == ("checkout", "-b") for command in create.commands)
1830
- assert any(command.args[:2] == ("stash", "push") for command in merge.commands)
1833
+ assert not any(command.args and command.args[0] == "stash" for command in merge.commands)
1834
+ assert any(command.args[:2] == ("status", "--porcelain") for command in merge.commands)
1831
1835
  assert any(command.args[:2] == ("merge", "--ff-only") for command in merge.commands)
1832
- assert any(command.args == ("push",) for command in merge.commands)
1833
- assert save_wip.never_fails is True
1834
- assert ensure.never_fails is True
1836
+ push_index = next(index for index, command in enumerate(merge.commands) if command.args == ("push",))
1837
+ merge_index = next(index for index, command in enumerate(merge.commands) if command.args[:2] == ("merge", "--ff-only"))
1838
+ assert merge_index < push_index
1839
+ assert save_wip.never_fails is False
1840
+ assert ensure.never_fails is False
1835
1841
  assert any(command.args[:2] == ("rebase", "--abort") for command in ensure.commands)
1836
1842
  assert any(command.args[:2] == ("add", "-A") for command in ensure.commands)
1837
- assert not any(command.args[:2] == ("stash", "pop") for command in ensure.commands)
1843
+ assert not any(command.args and command.args[0] == "stash" for command in ensure.commands)
1844
+ assert ensure.never_fails is False
1838
1845
  assert any(".*/worktrees/**" in " ".join(command.args) for command in save_wip.commands)
1839
1846
  assert worktree.commands[0].args[:2] == ("worktree", "add")
1840
1847
 
@@ -1862,6 +1869,205 @@ def test_git_branch_worktree_plans_preserve_shell_invariants(tmp_path):
1862
1869
  assert skipped_wip.results == ()
1863
1870
 
1864
1871
 
1872
+ def test_runtime_source_constructs_no_git_stash_commands():
1873
+ runtime = Path("dev-pipeline/prizmkit_runtime")
1874
+ matches = []
1875
+ for path in runtime.glob("*.py"):
1876
+ text = path.read_text(encoding="utf-8")
1877
+ if re.search(r'["\']stash["\']', text):
1878
+ matches.append(path.as_posix())
1879
+
1880
+ assert matches == []
1881
+
1882
+
1883
+ def _init_merge_repo(project_root):
1884
+ subprocess.run(["git", "init", "-b", "main"], cwd=project_root, check=True, stdout=subprocess.DEVNULL)
1885
+ subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=project_root, check=True)
1886
+ subprocess.run(["git", "config", "user.name", "Test"], cwd=project_root, check=True)
1887
+ manifest = project_root / ".prizmkit" / "manifest.json"
1888
+ manifest.parent.mkdir(parents=True)
1889
+ manifest.write_text('{"version":"current"}\n', encoding="utf-8")
1890
+ (project_root / "app.txt").write_text("base\n", encoding="utf-8")
1891
+ subprocess.run(["git", "add", "."], cwd=project_root, check=True)
1892
+ subprocess.run(["git", "commit", "-m", "base"], cwd=project_root, check=True, stdout=subprocess.DEVNULL)
1893
+
1894
+
1895
+ def _stash_oids(project_root):
1896
+ output = subprocess.run(
1897
+ ["git", "stash", "list", "--format=%H"],
1898
+ cwd=project_root,
1899
+ check=True,
1900
+ stdout=subprocess.PIPE,
1901
+ text=True,
1902
+ ).stdout
1903
+ return tuple(line for line in output.splitlines() if line)
1904
+
1905
+
1906
+ def test_branch_merge_preserves_preexisting_stash_when_workspace_is_clean(tmp_path):
1907
+ from prizmkit_runtime.gitops import BranchContext, branch_merge_plan, run_git_plan
1908
+
1909
+ _init_merge_repo(tmp_path)
1910
+ manifest = tmp_path / ".prizmkit" / "manifest.json"
1911
+ manifest.write_text('{"version":"stale"}\n', encoding="utf-8")
1912
+ subprocess.run(["git", "stash", "push", "-m", "stale-install", "--", ".prizmkit/manifest.json"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
1913
+ stale_stashes = _stash_oids(tmp_path)
1914
+ subprocess.run(["git", "checkout", "-b", "dev/example"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
1915
+ (tmp_path / "feature.txt").write_text("done\n", encoding="utf-8")
1916
+ subprocess.run(["git", "add", "feature.txt"], cwd=tmp_path, check=True)
1917
+ subprocess.run(["git", "commit", "-m", "feature"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
1918
+
1919
+ result = run_git_plan(
1920
+ tmp_path,
1921
+ branch_merge_plan(BranchContext("main", "dev/example", tmp_path)),
1922
+ )
1923
+
1924
+ assert result.ok is True
1925
+ assert _stash_oids(tmp_path) == stale_stashes
1926
+ assert json.loads(manifest.read_text(encoding="utf-8"))["version"] == "current"
1927
+ assert (tmp_path / "feature.txt").read_text(encoding="utf-8") == "done\n"
1928
+
1929
+
1930
+ def test_branch_merge_rejects_dirty_task_checkout_without_touching_stashes(tmp_path):
1931
+ from prizmkit_runtime.gitops import BranchContext, branch_merge_plan, run_git_plan
1932
+
1933
+ _init_merge_repo(tmp_path)
1934
+ manifest = tmp_path / ".prizmkit" / "manifest.json"
1935
+ manifest.write_text('{"version":"stale"}\n', encoding="utf-8")
1936
+ subprocess.run(["git", "stash", "push", "-m", "user-stash", "--", ".prizmkit/manifest.json"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
1937
+ stale_stashes = _stash_oids(tmp_path)
1938
+ subprocess.run(["git", "checkout", "-b", "dev/example"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
1939
+ (tmp_path / "feature.txt").write_text("done\n", encoding="utf-8")
1940
+ subprocess.run(["git", "add", "feature.txt"], cwd=tmp_path, check=True)
1941
+ subprocess.run(["git", "commit", "-m", "feature"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
1942
+ (tmp_path / "app.txt").write_text("local visible change\n", encoding="utf-8")
1943
+
1944
+ result = run_git_plan(
1945
+ tmp_path,
1946
+ branch_merge_plan(BranchContext("main", "dev/example", tmp_path)),
1947
+ )
1948
+
1949
+ assert result.ok is False
1950
+ assert _stash_oids(tmp_path) == stale_stashes
1951
+ assert (tmp_path / "app.txt").read_text(encoding="utf-8") == "local visible change\n"
1952
+ assert json.loads(manifest.read_text(encoding="utf-8"))["version"] == "current"
1953
+ assert subprocess.run(
1954
+ ["git", "branch", "--show-current"],
1955
+ cwd=tmp_path,
1956
+ check=True,
1957
+ stdout=subprocess.PIPE,
1958
+ text=True,
1959
+ ).stdout.strip() == "dev/example"
1960
+
1961
+
1962
+ def test_branch_merge_rebases_committed_task_without_stash_restore_conflict(tmp_path):
1963
+ from prizmkit_runtime.gitops import BranchContext, branch_merge_plan, run_git_plan
1964
+
1965
+ _init_merge_repo(tmp_path)
1966
+ subprocess.run(["git", "checkout", "-b", "dev/example"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
1967
+ (tmp_path / "feature.txt").write_text("done\n", encoding="utf-8")
1968
+ subprocess.run(["git", "add", "feature.txt"], cwd=tmp_path, check=True)
1969
+ subprocess.run(["git", "commit", "-m", "feature"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
1970
+ subprocess.run(["git", "checkout", "main"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
1971
+ (tmp_path / "app.txt").write_text("main changed\n", encoding="utf-8")
1972
+ subprocess.run(["git", "commit", "-am", "advance main"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
1973
+ subprocess.run(["git", "checkout", "dev/example"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
1974
+
1975
+ result = run_git_plan(
1976
+ tmp_path,
1977
+ branch_merge_plan(BranchContext("main", "dev/example", tmp_path)),
1978
+ )
1979
+
1980
+ assert result.ok is True
1981
+ assert _stash_oids(tmp_path) == ()
1982
+ assert (tmp_path / "app.txt").read_text(encoding="utf-8") == "main changed\n"
1983
+ assert subprocess.run(
1984
+ ["git", "branch", "--show-current"],
1985
+ cwd=tmp_path,
1986
+ check=True,
1987
+ stdout=subprocess.PIPE,
1988
+ text=True,
1989
+ ).stdout.strip() == "main"
1990
+
1991
+
1992
+ def test_branch_return_commits_wip_without_touching_user_stashes(tmp_path):
1993
+ from prizmkit_runtime.gitops import branch_ensure_return
1994
+
1995
+ _init_merge_repo(tmp_path)
1996
+ manifest = tmp_path / ".prizmkit" / "manifest.json"
1997
+ manifest.write_text('{"version":"stale"}\n', encoding="utf-8")
1998
+ subprocess.run(["git", "stash", "push", "-m", "user-stash", "--", ".prizmkit/manifest.json"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
1999
+ user_stashes = _stash_oids(tmp_path)
2000
+ subprocess.run(["git", "checkout", "-b", "dev/interrupted"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
2001
+ (tmp_path / "app.txt").write_text("interrupted work\n", encoding="utf-8")
2002
+
2003
+ result = branch_ensure_return(tmp_path, "main", "dev/interrupted")
2004
+
2005
+ assert result.ok is True
2006
+ assert _stash_oids(tmp_path) == user_stashes
2007
+ assert subprocess.run(
2008
+ ["git", "branch", "--show-current"], cwd=tmp_path, check=True, stdout=subprocess.PIPE, text=True,
2009
+ ).stdout.strip() == "main"
2010
+ assert subprocess.run(
2011
+ ["git", "show", "dev/interrupted:app.txt"], cwd=tmp_path, check=True, stdout=subprocess.PIPE, text=True,
2012
+ ).stdout == "interrupted work\n"
2013
+
2014
+
2015
+ def test_branch_return_commit_failure_does_not_checkout_base(monkeypatch, tmp_path):
2016
+ from prizmkit_runtime import gitops
2017
+
2018
+ calls = []
2019
+
2020
+ def fake_run(_root, args):
2021
+ args = tuple(args)
2022
+ calls.append(args)
2023
+ if args[:3] == ("rev-parse", "--abbrev-ref", "HEAD"):
2024
+ return gitops.GitCommandResult(args, 0, "dev/interrupted\n", "")
2025
+ if args and args[0] == "commit":
2026
+ return gitops.GitCommandResult(args, 1, "", "commit failed")
2027
+ if args[:2] == ("status", "--porcelain"):
2028
+ return gitops.GitCommandResult(args, 0, " M app.txt\n", "")
2029
+ return gitops.GitCommandResult(args, 0, "", "")
2030
+
2031
+ monkeypatch.setattr(gitops, "run_git_command", fake_run)
2032
+
2033
+ result = gitops.branch_ensure_return(tmp_path, "main", "dev/interrupted")
2034
+
2035
+ assert result.ok is False
2036
+ assert not any(args[:2] == ("checkout", "main") for args in calls)
2037
+ assert not any(args and args[0] == "stash" for args in calls)
2038
+
2039
+
2040
+ def test_non_current_reset_preserves_dirty_workspace_and_user_stashes(tmp_path):
2041
+ from prizmkit_runtime.gitops import reset_cleanup_branch
2042
+
2043
+ _init_merge_repo(tmp_path)
2044
+ subprocess.run(["git", "branch", "dev/other"], cwd=tmp_path, check=True)
2045
+ manifest = tmp_path / ".prizmkit" / "manifest.json"
2046
+ manifest.write_text('{"version":"stale"}\n', encoding="utf-8")
2047
+ subprocess.run(["git", "stash", "push", "-m", "user-stash", "--", ".prizmkit/manifest.json"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
2048
+ user_stashes = _stash_oids(tmp_path)
2049
+ (tmp_path / "app.txt").write_text("unstaged\n", encoding="utf-8")
2050
+ (tmp_path / "staged.txt").write_text("staged\n", encoding="utf-8")
2051
+ subprocess.run(["git", "add", "staged.txt"], cwd=tmp_path, check=True)
2052
+ (tmp_path / "untracked.txt").write_text("untracked\n", encoding="utf-8")
2053
+ before = subprocess.run(["git", "status", "--porcelain"], cwd=tmp_path, check=True, stdout=subprocess.PIPE, text=True).stdout
2054
+
2055
+ reset_result = reset_cleanup_branch(tmp_path, "dev/other", return_branch="main", delete=False)
2056
+
2057
+ assert reset_result.branch_preserved is True
2058
+ assert reset_result.checkout_discarded is False
2059
+ assert subprocess.run(["git", "branch", "--show-current"], cwd=tmp_path, check=True, stdout=subprocess.PIPE, text=True).stdout.strip() == "main"
2060
+ assert subprocess.run(["git", "status", "--porcelain"], cwd=tmp_path, check=True, stdout=subprocess.PIPE, text=True).stdout == before
2061
+ assert _stash_oids(tmp_path) == user_stashes
2062
+ assert subprocess.run(["git", "show-ref", "--verify", "refs/heads/dev/other"], cwd=tmp_path, check=False).returncode == 0
2063
+
2064
+ clean_result = reset_cleanup_branch(tmp_path, "dev/other", return_branch="main", delete=True)
2065
+
2066
+ assert clean_result.deleted is True
2067
+ assert subprocess.run(["git", "status", "--porcelain"], cwd=tmp_path, check=True, stdout=subprocess.PIPE, text=True).stdout == before
2068
+ assert _stash_oids(tmp_path) == user_stashes
2069
+
2070
+
1865
2071
  def test_worktree_runtime_context_uses_safe_deterministic_path(tmp_path):
1866
2072
  from prizmkit_runtime.gitops import BranchContext, WorktreePolicy, worktree_runtime_context
1867
2073
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1.128",
2
+ "version": "1.1.130",
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.",
@@ -361,8 +361,8 @@ Key requirements:
361
361
  - descriptions: minimum 15 words (error), recommended minimum 30/50/80/100+ for low/medium/high/critical (warning). No upper limit — more detail prevents AI guessing
362
362
  - `estimated_complexity` determines pipeline execution tier:
363
363
  - `low` / `medium` → **lite** (single agent, no subagents)
364
- - `high` → **standard** (orchestrator + reviewer guardrails, 3-agent metadata)
365
- - `critical` → **full** (strongest orchestrator/reviewer guardrails). Use for: architectural changes touching 10+ files, cross-module refactoring with API surface changes, or safety/security/data-loss risk
364
+ - `high` → **standard** (Main-Agent review guardrails, 3-agent compatibility metadata)
365
+ - `critical` → **full** (strongest Main-Agent review guardrails). Use for: architectural changes touching 10+ files, cross-module refactoring with API surface changes, or safety/security/data-loss risk
366
366
 
367
367
  **IMPORTANT: Do NOT hand-write the final JSON file.** Instead:
368
368
  1. Write a draft JSON to a temporary path (e.g., `.prizmkit/plans/feature-list.draft.json`)
@@ -110,7 +110,7 @@ PrizmKit supports any development scenario through the same skill chain. `/prizm
110
110
  | `/prizmkit-plan` | First implementation planning step: run ahead of `/prizmkit-implement` to turn natural language into `spec.md` + `plan.md` with executable tasks. | "specify", "plan", "new task", "architect", "break it down" |
111
111
  | `/prizmkit-implement` | Implementation step: use after `/prizmkit-plan` to execute `plan.md` tasks with TDD where applicable, task order, and checkpoints. | "implement", "build", "code it", "start coding" |
112
112
  | `/prizmkit-test` | Risk-triggered quality gate: run after implementation when behavior, interfaces, data, security, or deploy risk requires tests and boundary coverage. | "test", "run tests", "verify", "quality check", "boundary tests" |
113
- | `/prizmkit-code-review` | Full path quality gate: run after implementation; requires the dedicated reviewer agent in the active checkout and stops if that launch mode is unavailable. | "review", "check code", "code review", "is it ready to commit" |
113
+ | `/prizmkit-code-review` | Full path quality gate: the current Main Agent reviews and repairs the complete change in the active checkout without direct or indirect review delegation. | "review", "check code", "code review", "is it ready to commit" |
114
114
  | `/prizmkit-retrospective` | Docs maintenance step: run after review/implementation when structure, interfaces, dependencies, behavior, or durable knowledge changed. | "retrospective", "retro", "update docs", "sync docs", "wrap up" |
115
115
  | `/prizmkit-committer` | Final lifecycle commit step: use after required gates are satisfied; safely stages and creates a Conventional Commit without changing changelog by default. | "commit", "submit", "finish", "done" |
116
116
  | `/prizmkit-prizm-docs` | Documentation system entry point: use for init/status/rebuild/validate/migrate or out-of-band repair, not normal development sync. | "initialize docs", "check docs", "rebuild docs", "validate docs", "docs drifted" |
@@ -5,7 +5,16 @@ description: "Review the complete current change in a bounded Main-Agent loop. A
5
5
 
6
6
  # PrizmKit Code Review
7
7
 
8
- Use one Main-Agent review loop. The Main Agent performs the complete review, filters candidate findings, repairs accepted findings, verifies the repairs, and decides the final result without delegating code review.
8
+ Use one Main-Agent review loop. The current Main Agent is the only Code Review executor: it performs the complete review, filters candidate findings, repairs accepted findings, verifies the repairs, and decides the final result.
9
+
10
+ ## Execution Boundary
11
+
12
+ - Do not delegate any part of Code Review directly or indirectly.
13
+ - Do not invoke another review skill or review workflow from inside this skill.
14
+ - Do not launch review work through a general-purpose execution unit or relabel it as a finder, verifier, audit, compatibility review, verification, or gap sweep.
15
+ - These restrictions apply for the entire review phase, including candidate discovery, evidence checks, repair verification, and final gap analysis.
16
+ - The Main Agent may directly use non-delegating read, search, edit, and test capabilities in the active workspace.
17
+ - `{artifact_dir}/review-report.md` is the only persisted review artifact. Do not append separate Review Notes to `context-snapshot.md`.
9
18
 
10
19
  ## When to Use
11
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prizmkit",
3
- "version": "1.1.128",
3
+ "version": "1.1.130",
4
4
  "description": "Create a new PrizmKit-powered project with clean initialization — no framework dev files, just what you need.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/manifest.js CHANGED
@@ -19,6 +19,7 @@ const MANIFEST_DIR = '.prizmkit';
19
19
  * Read the manifest from a project directory.
20
20
  * @param {string} projectRoot
21
21
  * @returns {Promise<Object|null>} parsed manifest or null if not found
22
+ * @throws {Error} when a present manifest cannot be read or parsed
22
23
  */
23
24
  export async function readManifest(projectRoot) {
24
25
  const manifestPath = path.join(projectRoot, MANIFEST_DIR, MANIFEST_FILE);
@@ -26,9 +27,13 @@ export async function readManifest(projectRoot) {
26
27
  return null;
27
28
  }
28
29
  try {
29
- return await fs.readJSON(manifestPath);
30
- } catch {
31
- return null;
30
+ const manifest = await fs.readJSON(manifestPath);
31
+ if (manifest === null || Array.isArray(manifest) || typeof manifest !== 'object') {
32
+ throw new TypeError('manifest root must be a JSON object');
33
+ }
34
+ return manifest;
35
+ } catch (error) {
36
+ throw new Error(`Failed to read PrizmKit manifest at ${manifestPath}: ${error.message}`, { cause: error });
32
37
  }
33
38
  }
34
39