prizmkit 1.1.108 → 1.1.110
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/cli.py +20 -0
- package/bundled/dev-pipeline/prizmkit_runtime/commands.py +1 -0
- package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +21 -0
- package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +1 -0
- package/bundled/dev-pipeline/prizmkit_runtime/platform_detection.py +208 -0
- package/bundled/dev-pipeline/prizmkit_runtime/runtime_helper.py +508 -0
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +3 -1
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +3 -1
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +3 -1
- package/bundled/dev-pipeline/scripts/prizmkit-runtime-helper.py +27 -0
- package/bundled/dev-pipeline/scripts/utils.py +48 -77
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +44 -71
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +48 -75
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +52 -78
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +2 -2
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +2 -2
- package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +1 -1
- package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +8 -8
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification-auto.md +33 -55
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification-opencli.md +28 -33
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +22 -62
- package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +2 -2
- package/bundled/dev-pipeline/templates/sections/phase-commit.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-base.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-critic-plan-full.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-critic-plan.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-plan-agent.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +2 -2
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +7 -6
- package/bundled/dev-pipeline/templates/sections/phase0-init.md +1 -1
- package/bundled/dev-pipeline/templates/sections/subagent-timeout-recovery.md +1 -1
- package/bundled/dev-pipeline/templates/sections/test-failure-recovery-agent.md +1 -1
- package/bundled/dev-pipeline/templates/sections/test-failure-recovery-lite.md +1 -1
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +152 -3
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +2 -1
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +2 -1
- package/bundled/dev-pipeline/tests/test_runtime_helper.py +211 -0
- package/bundled/dev-pipeline/tests/test_unified_cli.py +5 -0
- package/bundled/skills/_metadata.json +1 -1
- package/package.json +1 -1
package/bundled/VERSION.json
CHANGED
|
@@ -9,6 +9,7 @@ from collections.abc import Sequence
|
|
|
9
9
|
from .commands import CommandResult, handle_diagnostics, handle_runtime_command
|
|
10
10
|
from .compat import UnsupportedPythonVersion, ensure_supported_python
|
|
11
11
|
from .paths import resolve_runtime_paths
|
|
12
|
+
from .runtime_helper import render_result, run_helper_command
|
|
12
13
|
|
|
13
14
|
DESCRIPTION = (
|
|
14
15
|
"PrizmKit canonical Python runtime CLI.\n"
|
|
@@ -81,6 +82,16 @@ def _add_diagnostics_subparsers(subparsers: argparse._SubParsersAction[argparse.
|
|
|
81
82
|
action_parser.set_defaults(handler="diagnostics", action=action)
|
|
82
83
|
|
|
83
84
|
|
|
85
|
+
def _add_helper_subparser(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
86
|
+
helper_parser = subparsers.add_parser("helper", help="Cross-platform prompt runtime helper commands")
|
|
87
|
+
helper_parser.add_argument(
|
|
88
|
+
"args",
|
|
89
|
+
nargs=argparse.REMAINDER,
|
|
90
|
+
help="Arguments forwarded to prizmkit-runtime-helper.",
|
|
91
|
+
)
|
|
92
|
+
helper_parser.set_defaults(handler="helper", group="helper", action="run", target=None)
|
|
93
|
+
|
|
94
|
+
|
|
84
95
|
_PIPELINE_GROUPS = {"feature", "bugfix", "refactor", "recovery"}
|
|
85
96
|
_GLOBAL_OPTIONS_WITH_VALUES = {"--project-root", "--pipeline-root"}
|
|
86
97
|
|
|
@@ -150,6 +161,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
150
161
|
_add_daemon_subparsers(subparsers)
|
|
151
162
|
_add_status_subparsers(subparsers)
|
|
152
163
|
_add_diagnostics_subparsers(subparsers)
|
|
164
|
+
_add_helper_subparser(subparsers)
|
|
153
165
|
return parser
|
|
154
166
|
|
|
155
167
|
|
|
@@ -161,6 +173,14 @@ def _dispatch(args: argparse.Namespace) -> CommandResult:
|
|
|
161
173
|
paths = _runtime_paths_from_args(args)
|
|
162
174
|
if args.handler == "diagnostics":
|
|
163
175
|
return handle_diagnostics(args.action, paths)
|
|
176
|
+
if args.handler == "helper":
|
|
177
|
+
helper_result = run_helper_command(tuple(getattr(args, "args", ()) or ()))
|
|
178
|
+
return CommandResult(
|
|
179
|
+
helper_result.exit_code,
|
|
180
|
+
"Python runtime helper",
|
|
181
|
+
"Cross-platform prompt runtime helper result.",
|
|
182
|
+
stdout=render_result(helper_result, json_output="--json" in tuple(getattr(args, "args", ()) or ())),
|
|
183
|
+
)
|
|
164
184
|
return handle_runtime_command(
|
|
165
185
|
args.group,
|
|
166
186
|
args.action,
|
|
@@ -280,6 +280,7 @@ def diagnostics_entrypoints(paths: RuntimePaths | None = None) -> CommandResult:
|
|
|
280
280
|
details: list[str] = [
|
|
281
281
|
f"runtime:cli: {runtime_paths.pipeline_root / 'cli.py'}",
|
|
282
282
|
f"runtime:module: {runtime_paths.pipeline_root / 'prizmkit_runtime'}",
|
|
283
|
+
f"runtime:helper-module: {runtime_paths.pipeline_root / 'prizmkit_runtime' / 'runtime_helper.py'}",
|
|
283
284
|
]
|
|
284
285
|
for name, relative_path in sorted(UTILITY_ENTRYPOINTS.items()):
|
|
285
286
|
invocation = describe_existing_entrypoint("utility", name, paths=runtime_paths)
|
|
@@ -33,6 +33,8 @@ HIDDEN_TOOL_WORKTREE_EXCLUDES = (
|
|
|
33
33
|
":(top,exclude).prizmkit/manifest.json",
|
|
34
34
|
":(top,exclude).prizmkit/dev-pipeline/scripts",
|
|
35
35
|
":(top,exclude,glob).prizmkit/dev-pipeline/scripts/**",
|
|
36
|
+
":(top,exclude).prizmkit/dev-pipeline/prizmkit_runtime",
|
|
37
|
+
":(top,exclude,glob).prizmkit/dev-pipeline/prizmkit_runtime/**",
|
|
36
38
|
)
|
|
37
39
|
|
|
38
40
|
WORKTREE_PLATFORM_SUPPORT_DIRS = (".claude", ".codebuddy", ".codex", ".agents")
|
|
@@ -242,6 +244,14 @@ def materialize_worktree_support_assets(runtime: WorktreeRuntimeContext) -> Work
|
|
|
242
244
|
target = target_root / ".prizmkit" / "dev-pipeline" / "scripts"
|
|
243
245
|
changed = _copy_support_entry(runtime_scripts, target)
|
|
244
246
|
(copied if changed else existing).append(target)
|
|
247
|
+
|
|
248
|
+
runtime_package = _runtime_support_package_source(source_root)
|
|
249
|
+
if runtime_package is None:
|
|
250
|
+
missing.append(".prizmkit/dev-pipeline/prizmkit_runtime")
|
|
251
|
+
else:
|
|
252
|
+
target = target_root / ".prizmkit" / "dev-pipeline" / "prizmkit_runtime"
|
|
253
|
+
changed = _copy_support_entry(runtime_package, target)
|
|
254
|
+
(copied if changed else existing).append(target)
|
|
245
255
|
except OSError as exc:
|
|
246
256
|
return WorktreeSupportAssetsResult(False, tuple(copied), tuple(existing), tuple(missing), str(exc))
|
|
247
257
|
|
|
@@ -276,6 +286,17 @@ def _runtime_support_scripts_source(project_root: Path) -> Path | None:
|
|
|
276
286
|
return None
|
|
277
287
|
|
|
278
288
|
|
|
289
|
+
def _runtime_support_package_source(project_root: Path) -> Path | None:
|
|
290
|
+
candidates = (
|
|
291
|
+
project_root / ".prizmkit" / "dev-pipeline" / "prizmkit_runtime",
|
|
292
|
+
project_root / "dev-pipeline" / "prizmkit_runtime",
|
|
293
|
+
)
|
|
294
|
+
for candidate in candidates:
|
|
295
|
+
if candidate.is_dir():
|
|
296
|
+
return candidate
|
|
297
|
+
return None
|
|
298
|
+
|
|
299
|
+
|
|
279
300
|
def _copy_support_entry(source: Path, target: Path) -> bool:
|
|
280
301
|
if source.is_symlink():
|
|
281
302
|
if target.exists() or target.is_symlink():
|
|
@@ -24,6 +24,7 @@ UTILITY_ENTRYPOINTS = {
|
|
|
24
24
|
"init-refactor-pipeline": "scripts/init-refactor-pipeline.py",
|
|
25
25
|
"parse-stream-progress": "scripts/parse-stream-progress.py",
|
|
26
26
|
"patch-completion-notes": "scripts/patch-completion-notes.py",
|
|
27
|
+
"prizmkit-runtime-helper": "scripts/prizmkit-runtime-helper.py",
|
|
27
28
|
"update-bug-status": "scripts/update-bug-status.py",
|
|
28
29
|
"update-checkpoint": "scripts/update-checkpoint.py",
|
|
29
30
|
"update-feature-status": "scripts/update-feature-status.py",
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Shared AI platform and skill path detection for prompt/runtime helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from collections.abc import Mapping
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
SUPPORTED_PLATFORMS = ("claude", "codex", "codebuddy")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class PlatformResolution:
|
|
16
|
+
"""Resolved AI platform plus the source that selected it."""
|
|
17
|
+
|
|
18
|
+
platform: str
|
|
19
|
+
source: str
|
|
20
|
+
notes: tuple[str, ...] = ()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class SkillCandidate:
|
|
25
|
+
"""One possible filesystem location for a skill."""
|
|
26
|
+
|
|
27
|
+
platform: str
|
|
28
|
+
scope: str
|
|
29
|
+
path: Path
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class SkillResolution:
|
|
34
|
+
"""Resolved skill lookup result."""
|
|
35
|
+
|
|
36
|
+
marker: str
|
|
37
|
+
platform: str
|
|
38
|
+
skill: str
|
|
39
|
+
path: Path | None
|
|
40
|
+
candidates: tuple[SkillCandidate, ...]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
_PLATFORM_MARKERS = {
|
|
44
|
+
"claude": (".claude/agents", ".claude/commands", ".claude/skills"),
|
|
45
|
+
"codex": (".codex/agents", ".agents/skills"),
|
|
46
|
+
"codebuddy": (".codebuddy/agents", ".codebuddy/skills"),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
_PLATFORM_SKILL_PATTERNS = {
|
|
51
|
+
"claude": (
|
|
52
|
+
("project-command-file", ".claude/commands/{skill}.md"),
|
|
53
|
+
("project-command-dir", ".claude/commands/{skill}"),
|
|
54
|
+
("project-skill", ".claude/skills/{skill}"),
|
|
55
|
+
("home-skill", "~/.claude/skills/{skill}"),
|
|
56
|
+
),
|
|
57
|
+
"codex": (
|
|
58
|
+
("project-skill", ".agents/skills/{skill}"),
|
|
59
|
+
("project-codex-skill", ".codex/skills/{skill}"),
|
|
60
|
+
),
|
|
61
|
+
"codebuddy": (
|
|
62
|
+
("project-skill", ".codebuddy/skills/{skill}"),
|
|
63
|
+
("home-skill", "~/.codebuddy/skills/{skill}"),
|
|
64
|
+
("legacy-home-skill", "~/.cbc/skills/{skill}"),
|
|
65
|
+
),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def read_json_object(path: str | Path) -> dict:
|
|
70
|
+
"""Read a JSON object, returning an empty dict when unavailable."""
|
|
71
|
+
try:
|
|
72
|
+
with Path(path).expanduser().open("r", encoding="utf-8") as handle:
|
|
73
|
+
data = json.load(handle)
|
|
74
|
+
except (OSError, json.JSONDecodeError):
|
|
75
|
+
return {}
|
|
76
|
+
return data if isinstance(data, dict) else {}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def platform_from_command(command: object) -> str:
|
|
80
|
+
"""Map an AI CLI command/config value to a PrizmKit platform name."""
|
|
81
|
+
cmd = str(command or "").strip().lower()
|
|
82
|
+
if not cmd:
|
|
83
|
+
return ""
|
|
84
|
+
base = os.path.basename(cmd.split()[0])
|
|
85
|
+
if base in ("claude", "claude-code") or "claude" in base:
|
|
86
|
+
return "claude"
|
|
87
|
+
if base in ("codex", "openai-codex") or "codex" in base:
|
|
88
|
+
return "codex"
|
|
89
|
+
if base in ("cbc", "codebuddy") or "codebuddy" in base:
|
|
90
|
+
return "codebuddy"
|
|
91
|
+
return ""
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def valid_prompt_platform(value: object) -> str:
|
|
95
|
+
"""Normalize a platform value for prompt path rendering."""
|
|
96
|
+
platform = str(value or "").strip().lower()
|
|
97
|
+
return platform if platform in SUPPORTED_PLATFORMS else ""
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def resolve_platform(project_root: str | Path, env: Mapping[str, str] | None = None) -> PlatformResolution:
|
|
101
|
+
"""Resolve the platform whose paths should appear in prompts/helper output.
|
|
102
|
+
|
|
103
|
+
Precedence mirrors prompt generation: explicit environment, active/configured
|
|
104
|
+
AI CLI, PrizmKit config/manifest platform, then filesystem markers.
|
|
105
|
+
"""
|
|
106
|
+
environment = os.environ if env is None else env
|
|
107
|
+
root = Path(project_root).expanduser().resolve()
|
|
108
|
+
|
|
109
|
+
explicit = valid_prompt_platform(environment.get("PRIZMKIT_PLATFORM", ""))
|
|
110
|
+
if explicit:
|
|
111
|
+
return PlatformResolution(explicit, "environment:PRIZMKIT_PLATFORM")
|
|
112
|
+
|
|
113
|
+
for env_name in ("AI_CLI", "CLAUDECODE", "CLAUDE_CODE", "CODEBUDDY_CLI"):
|
|
114
|
+
detected = platform_from_command(environment.get(env_name, ""))
|
|
115
|
+
if detected:
|
|
116
|
+
return PlatformResolution(detected, f"environment:{env_name}")
|
|
117
|
+
|
|
118
|
+
config = read_json_object(root / ".prizmkit" / "config.json")
|
|
119
|
+
for key in ("ai_cli", "aiCli"):
|
|
120
|
+
detected = platform_from_command(config.get(key, ""))
|
|
121
|
+
if detected:
|
|
122
|
+
return PlatformResolution(detected, f"config:{key}")
|
|
123
|
+
detected = valid_prompt_platform(config.get("platform", ""))
|
|
124
|
+
if detected:
|
|
125
|
+
return PlatformResolution(detected, "config:platform")
|
|
126
|
+
|
|
127
|
+
manifest = read_json_object(root / ".prizmkit" / "manifest.json")
|
|
128
|
+
options = manifest.get("options", {}) if isinstance(manifest.get("options"), dict) else {}
|
|
129
|
+
for key, value in (("aiCli", manifest.get("aiCli", "")), ("options.aiCli", options.get("aiCli", ""))):
|
|
130
|
+
detected = platform_from_command(value)
|
|
131
|
+
if detected:
|
|
132
|
+
return PlatformResolution(detected, f"manifest:{key}")
|
|
133
|
+
detected = valid_prompt_platform(manifest.get("platform", ""))
|
|
134
|
+
if detected:
|
|
135
|
+
return PlatformResolution(detected, "manifest:platform")
|
|
136
|
+
|
|
137
|
+
for platform in SUPPORTED_PLATFORMS:
|
|
138
|
+
for marker in _PLATFORM_MARKERS[platform]:
|
|
139
|
+
if (root / marker).is_dir():
|
|
140
|
+
return PlatformResolution(platform, f"filesystem:{marker}")
|
|
141
|
+
|
|
142
|
+
return PlatformResolution(
|
|
143
|
+
"",
|
|
144
|
+
"unresolved",
|
|
145
|
+
(
|
|
146
|
+
"No supported platform marker found. Set PRIZMKIT_PLATFORM, configure .prizmkit/config.json, "
|
|
147
|
+
"or install Claude/Codex/CodeBuddy platform files.",
|
|
148
|
+
),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _expand_skill_pattern(project_root: Path, skill: str, platform: str, scope: str, pattern: str) -> SkillCandidate:
|
|
153
|
+
rendered = pattern.format(skill=skill)
|
|
154
|
+
if rendered.startswith("~"):
|
|
155
|
+
path = Path(rendered).expanduser()
|
|
156
|
+
else:
|
|
157
|
+
path = project_root / rendered
|
|
158
|
+
return SkillCandidate(platform=platform, scope=scope, path=path.resolve())
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def skill_candidates(project_root: str | Path, skill: str, platform: str) -> tuple[SkillCandidate, ...]:
|
|
162
|
+
"""Return deterministic candidate locations for a skill on one platform."""
|
|
163
|
+
normalized = valid_prompt_platform(platform)
|
|
164
|
+
if not normalized:
|
|
165
|
+
return ()
|
|
166
|
+
root = Path(project_root).expanduser().resolve()
|
|
167
|
+
return tuple(
|
|
168
|
+
_expand_skill_pattern(root, skill, normalized, scope, pattern)
|
|
169
|
+
for scope, pattern in _PLATFORM_SKILL_PATTERNS[normalized]
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _candidate_matches(candidate: SkillCandidate, skill: str, *, prefix: bool) -> bool:
|
|
174
|
+
path = candidate.path
|
|
175
|
+
if path.exists():
|
|
176
|
+
return True
|
|
177
|
+
if not prefix:
|
|
178
|
+
return False
|
|
179
|
+
parent = path.parent
|
|
180
|
+
if not parent.is_dir():
|
|
181
|
+
return False
|
|
182
|
+
return any(child.name.startswith(skill) for child in parent.iterdir())
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def resolve_skill(
|
|
186
|
+
project_root: str | Path,
|
|
187
|
+
skill: str,
|
|
188
|
+
*,
|
|
189
|
+
platform: str = "auto",
|
|
190
|
+
env: Mapping[str, str] | None = None,
|
|
191
|
+
prefix: bool = False,
|
|
192
|
+
) -> SkillResolution:
|
|
193
|
+
"""Resolve a skill path for the selected platform."""
|
|
194
|
+
normalized_skill = str(skill or "").strip()
|
|
195
|
+
selected_platform = valid_prompt_platform(platform)
|
|
196
|
+
if platform == "auto" or not platform:
|
|
197
|
+
selected_platform = resolve_platform(project_root, env=env).platform
|
|
198
|
+
if not selected_platform:
|
|
199
|
+
return SkillResolution("NOT_INSTALLED", "", normalized_skill, None, ())
|
|
200
|
+
|
|
201
|
+
candidates = skill_candidates(project_root, normalized_skill, selected_platform)
|
|
202
|
+
for candidate in candidates:
|
|
203
|
+
if _candidate_matches(candidate, normalized_skill, prefix=prefix):
|
|
204
|
+
if candidate.path.exists():
|
|
205
|
+
return SkillResolution("SKILL_EXISTS", selected_platform, normalized_skill, candidate.path, candidates)
|
|
206
|
+
matches = sorted(child for child in candidate.path.parent.iterdir() if child.name.startswith(normalized_skill))
|
|
207
|
+
return SkillResolution("SKILL_EXISTS", selected_platform, normalized_skill, matches[0].resolve(), candidates)
|
|
208
|
+
return SkillResolution("SKILL_MISSING", selected_platform, normalized_skill, None, candidates)
|