prizmkit 1.1.101 → 1.1.102
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/runner_models.py +10 -0
- package/bundled/dev-pipeline/prizmkit_runtime/runner_status.py +9 -3
- package/bundled/dev-pipeline/prizmkit_runtime/runners.py +4 -1
- package/bundled/dev-pipeline/prizmkit_runtime/sessions.py +10 -2
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +13 -13
- package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +1 -1
- package/bundled/dev-pipeline/templates/agent-prompts/reviewer-review.md +1 -1
- package/bundled/dev-pipeline/templates/bootstrap-prompt.md +2 -2
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +6 -7
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +1 -1
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +7 -7
- package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +7 -7
- package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +8 -17
- package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +2 -2
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +1 -3
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +1 -3
- package/bundled/dev-pipeline/templates/sections/phase0-test-baseline.md +3 -8
- package/bundled/dev-pipeline/templates/sections/test-failure-recovery-agent.md +2 -2
- package/bundled/dev-pipeline/templates/sections/test-failure-recovery-lite.md +1 -1
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +11 -7
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +89 -1
- package/bundled/skills/_metadata.json +1 -1
- package/package.json +1 -1
- package/src/config.js +9 -7
- package/src/external-skills.js +14 -14
- package/src/manifest.js +8 -3
- package/src/platforms.js +25 -0
- package/src/scaffold.js +11 -6
- package/src/upgrade.js +11 -2
package/bundled/VERSION.json
CHANGED
|
@@ -45,6 +45,7 @@ class RunnerEnvironment:
|
|
|
45
45
|
heartbeat_stale_threshold_seconds: int = 600
|
|
46
46
|
stale_kill_threshold_seconds: int = 600
|
|
47
47
|
max_log_size: int | None = None
|
|
48
|
+
max_retries: int = 3
|
|
48
49
|
strict_behavior_check: bool = True
|
|
49
50
|
|
|
50
51
|
@classmethod
|
|
@@ -65,6 +66,7 @@ class RunnerEnvironment:
|
|
|
65
66
|
),
|
|
66
67
|
stale_kill_threshold_seconds=_int_value(values, "STALE_KILL_THRESHOLD", 600),
|
|
67
68
|
max_log_size=_optional_int(values.get("MAX_LOG_SIZE")),
|
|
69
|
+
max_retries=max(0, _int_value(values, "MAX_RETRIES", 3)),
|
|
68
70
|
strict_behavior_check=not _falsey(values.get("STRICT_BEHAVIOR_CHECK")),
|
|
69
71
|
)
|
|
70
72
|
|
|
@@ -77,6 +79,7 @@ class RunnerInvocation:
|
|
|
77
79
|
list_path: Path
|
|
78
80
|
item_id: str = ""
|
|
79
81
|
item_filter: str = ""
|
|
82
|
+
max_retries: int | None = None
|
|
80
83
|
max_infra_retries: int = 3
|
|
81
84
|
mode: str | None = None
|
|
82
85
|
critic: bool | None = None
|
|
@@ -228,6 +231,7 @@ def parse_invocation(
|
|
|
228
231
|
list_path = family.plan_path
|
|
229
232
|
item_id = ""
|
|
230
233
|
item_filter = ""
|
|
234
|
+
max_retries: int | None = None
|
|
231
235
|
max_infra_retries = 3
|
|
232
236
|
mode: str | None = None
|
|
233
237
|
critic: bool | None = None
|
|
@@ -259,6 +263,11 @@ def parse_invocation(
|
|
|
259
263
|
item_filter = args[index]
|
|
260
264
|
elif arg.startswith("--features="):
|
|
261
265
|
item_filter = arg.split("=", 1)[1]
|
|
266
|
+
elif arg == "--max-retries" and index + 1 < len(args):
|
|
267
|
+
index += 1
|
|
268
|
+
max_retries = max(0, _safe_int(args[index], 3))
|
|
269
|
+
elif arg.startswith("--max-retries="):
|
|
270
|
+
max_retries = max(0, _safe_int(arg.split("=", 1)[1], 3))
|
|
262
271
|
elif arg == "--max-infra-retries" and index + 1 < len(args):
|
|
263
272
|
index += 1
|
|
264
273
|
max_infra_retries = _safe_int(args[index], 3)
|
|
@@ -293,6 +302,7 @@ def parse_invocation(
|
|
|
293
302
|
list_path=list_path,
|
|
294
303
|
item_id=item_id,
|
|
295
304
|
item_filter=item_filter,
|
|
305
|
+
max_retries=max_retries,
|
|
296
306
|
max_infra_retries=max_infra_retries,
|
|
297
307
|
mode=mode,
|
|
298
308
|
critic=critic,
|
|
@@ -8,7 +8,7 @@ import sys
|
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
|
-
from .runner_models import RunnerFamily, RunnerInvocation
|
|
11
|
+
from .runner_models import RunnerEnvironment, RunnerFamily, RunnerInvocation
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@dataclass(frozen=True)
|
|
@@ -30,6 +30,12 @@ class StatusCommandResult:
|
|
|
30
30
|
return self.stdout.strip() or self.stderr.strip()
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
def _resolved_max_retries(invocation: RunnerInvocation) -> int:
|
|
34
|
+
if invocation.max_retries is not None:
|
|
35
|
+
return max(0, invocation.max_retries)
|
|
36
|
+
return RunnerEnvironment.from_env().max_retries
|
|
37
|
+
|
|
38
|
+
|
|
33
39
|
def run_status_action(
|
|
34
40
|
family: RunnerFamily,
|
|
35
41
|
action: str,
|
|
@@ -38,7 +44,7 @@ def run_status_action(
|
|
|
38
44
|
item_id: str = "",
|
|
39
45
|
session_status: str = "",
|
|
40
46
|
session_id: str = "",
|
|
41
|
-
max_retries: int =
|
|
47
|
+
max_retries: int | None = None,
|
|
42
48
|
active_dev_branch: str = "",
|
|
43
49
|
base_branch: str = "",
|
|
44
50
|
last_fatal_error_code: str = "",
|
|
@@ -47,7 +53,7 @@ def run_status_action(
|
|
|
47
53
|
progress_fingerprint: object | None = None,
|
|
48
54
|
project_root: Path | None = None,
|
|
49
55
|
) -> StatusCommandResult:
|
|
50
|
-
|
|
56
|
+
max_retries = _resolved_max_retries(invocation) if max_retries is None else max(0, max_retries)
|
|
51
57
|
command = [
|
|
52
58
|
sys.executable,
|
|
53
59
|
str(family.updater_script),
|
|
@@ -114,6 +114,8 @@ def _run_pipeline(family: RunnerFamily, invocation: RunnerInvocation, paths) ->
|
|
|
114
114
|
details.append(f"{item_id}:{final_status}")
|
|
115
115
|
if final_status == "completed":
|
|
116
116
|
continue
|
|
117
|
+
if final_status == "pending":
|
|
118
|
+
continue
|
|
117
119
|
if RunnerEnvironment.from_env().stop_on_failure:
|
|
118
120
|
return PipelineRunResult(False, processed, "stop_on_failure", final_status, tuple(details))
|
|
119
121
|
if final_status in {"failed", "needs_info"}:
|
|
@@ -401,6 +403,7 @@ def _resolve_invocation_paths(invocation: RunnerInvocation, project_root: Path)
|
|
|
401
403
|
list_path=(project_root / invocation.list_path).resolve(),
|
|
402
404
|
item_id=invocation.item_id,
|
|
403
405
|
item_filter=invocation.item_filter,
|
|
406
|
+
max_retries=invocation.max_retries,
|
|
404
407
|
max_infra_retries=invocation.max_infra_retries,
|
|
405
408
|
mode=invocation.mode,
|
|
406
409
|
critic=invocation.critic,
|
|
@@ -418,5 +421,5 @@ def _help_text(family: RunnerFamily) -> str:
|
|
|
418
421
|
return (
|
|
419
422
|
f"Python foreground runner for {family.kind}.\n"
|
|
420
423
|
"Supported legacy options include plan list path, item id, --features for feature, "
|
|
421
|
-
"--max-infra-retries, --mode, --critic/--no-critic, --resume-phase, and --dry-run."
|
|
424
|
+
"--max-retries, --max-infra-retries, --mode, --critic/--no-critic, --resume-phase, and --dry-run."
|
|
422
425
|
)
|
|
@@ -152,7 +152,14 @@ def build_ai_session_command(config: AISessionConfig) -> AISessionCommand:
|
|
|
152
152
|
platform = _normalize_platform(config.platform)
|
|
153
153
|
prompt = _prompt_text(config.prompt_path)
|
|
154
154
|
if platform == "claude":
|
|
155
|
-
argv = [
|
|
155
|
+
argv = [
|
|
156
|
+
config.cli_command,
|
|
157
|
+
"-p",
|
|
158
|
+
prompt,
|
|
159
|
+
"--dangerously-skip-permissions",
|
|
160
|
+
"--disallowedTools",
|
|
161
|
+
"EnterWorktree",
|
|
162
|
+
]
|
|
156
163
|
if config.verbose or config.use_stream_json:
|
|
157
164
|
argv.append("--verbose")
|
|
158
165
|
if config.use_stream_json:
|
|
@@ -175,6 +182,7 @@ def build_ai_session_command(config: AISessionConfig) -> AISessionCommand:
|
|
|
175
182
|
timeout = _codex_subagent_timeout(config)
|
|
176
183
|
if timeout > 0:
|
|
177
184
|
argv.extend(["--config", f"agents.job_max_runtime_seconds={timeout}"])
|
|
185
|
+
argv.extend(["--profile", "no-worktree"])
|
|
178
186
|
argv.extend(["exec", "--cd", str(config.cwd), "--skip-git-repo-check"])
|
|
179
187
|
if config.use_stream_json:
|
|
180
188
|
argv.append("--json")
|
|
@@ -186,7 +194,7 @@ def build_ai_session_command(config: AISessionConfig) -> AISessionCommand:
|
|
|
186
194
|
argv.append("-")
|
|
187
195
|
return AISessionCommand(tuple(argv), prompt, "codex", "stdin")
|
|
188
196
|
|
|
189
|
-
argv = [config.cli_command, "--print", "-y"]
|
|
197
|
+
argv = [config.cli_command, "--print", "-y", "--disallowedTools", "EnterWorktree"]
|
|
190
198
|
if config.verbose:
|
|
191
199
|
argv.append("--verbose")
|
|
192
200
|
if config.use_stream_json:
|
|
@@ -676,7 +676,7 @@ def determine_pipeline_mode(complexity):
|
|
|
676
676
|
SECTION_TO_SKILL = {
|
|
677
677
|
"phase0-init": ("prizmkit-init", "Project Bootstrap",
|
|
678
678
|
[".prizmkit/prizm-docs/root.prizm", ".prizmkit/config.json"]),
|
|
679
|
-
"phase0-test-baseline": ("test-baseline", "Test
|
|
679
|
+
"phase0-test-baseline": ("test-baseline", "Detect Test Commands", []),
|
|
680
680
|
"phase-context-snapshot": ("context-snapshot", "Build Context Snapshot",
|
|
681
681
|
[".prizmkit/specs/{slug}/context-snapshot.md"]),
|
|
682
682
|
"phase-specify-plan": ("context-snapshot-and-plan", "Specify & Plan",
|
|
@@ -1559,6 +1559,18 @@ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
|
|
|
1559
1559
|
load_section(sections_dir,
|
|
1560
1560
|
"phase-implement-agent.md")))
|
|
1561
1561
|
|
|
1562
|
+
# Verification Gates are included in Task Contract. Keep AC in one place so
|
|
1563
|
+
# background context and implementation prompts cannot redefine scope.
|
|
1564
|
+
# --- Review ---
|
|
1565
|
+
if pipeline_mode == "full":
|
|
1566
|
+
sections.append(("phase-review",
|
|
1567
|
+
load_section(sections_dir,
|
|
1568
|
+
"phase-review-full.md")))
|
|
1569
|
+
else:
|
|
1570
|
+
sections.append(("phase-review",
|
|
1571
|
+
load_section(sections_dir,
|
|
1572
|
+
"phase-review-agent.md")))
|
|
1573
|
+
|
|
1562
1574
|
# --- Test Failure Recovery Protocol (tier-specific) ---
|
|
1563
1575
|
if pipeline_mode == "lite":
|
|
1564
1576
|
sections.append(("test-failure-recovery",
|
|
@@ -1574,18 +1586,6 @@ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
|
|
|
1574
1586
|
load_section(sections_dir,
|
|
1575
1587
|
"phase-prizmkit-test.md")))
|
|
1576
1588
|
|
|
1577
|
-
# Verification Gates are included in Task Contract. Keep AC in one place so
|
|
1578
|
-
# background context and implementation prompts cannot redefine scope.
|
|
1579
|
-
# --- Review (only for agent tiers) ---
|
|
1580
|
-
if pipeline_mode == "full":
|
|
1581
|
-
sections.append(("phase-review",
|
|
1582
|
-
load_section(sections_dir,
|
|
1583
|
-
"phase-review-full.md")))
|
|
1584
|
-
elif pipeline_mode == "standard":
|
|
1585
|
-
sections.append(("phase-review",
|
|
1586
|
-
load_section(sections_dir,
|
|
1587
|
-
"phase-review-agent.md")))
|
|
1588
|
-
|
|
1589
1589
|
# --- Browser Verification (conditional, tool-aware) ---
|
|
1590
1590
|
if browser_enabled:
|
|
1591
1591
|
if browser_tool == "opencli":
|
|
@@ -61,7 +61,7 @@ Before returning, append `## Implementation Log` to `context-snapshot.md` with:
|
|
|
61
61
|
- Do not run git commands; staging and commit are handled by the orchestrator.
|
|
62
62
|
- **Edit safety**: If an Edit fails with 'String to replace not found', grep for the target text before retrying. Never guess file offsets — verify them with a Read or grep first.
|
|
63
63
|
- **Read safety**: If 3 consecutive Reads to the same file return 'shorter than offset' or 'Wasted call', STOP and report BLOCKED.
|
|
64
|
-
- **Test
|
|
64
|
+
- **Test gate placement**: Do not run mandatory periodic or full-suite tests here. The scoped/full test gate runs after code review; only use tiny targeted checks when needed for local debugging.
|
|
65
65
|
|
|
66
66
|
Do not return success unless:
|
|
67
67
|
1. implementation tasks are complete;
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
1. Read `.prizmkit/specs/{{FEATURE_SLUG}}/spec.md` (if it exists) for goals and Verification Gates; if spec.md does not exist, read `.prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md` Section 1 Task Contract instead
|
|
3
3
|
2. Read `.prizmkit/specs/{{FEATURE_SLUG}}/plan.md` for architecture decisions and completed tasks
|
|
4
4
|
3. Run /prizmkit-code-review with artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/. The skill will run its internal review-fix loop (Reviewer → filter → Dev fix, max 3 rounds) and write review-report.md.
|
|
5
|
-
4.
|
|
5
|
+
4. Do not run test suites here. The orchestrator runs `/prizmkit-test` after review completes and then fixes code/tests until the scoped gate passes.
|
|
6
6
|
5. review-report.md will be written to .prizmkit/specs/{{FEATURE_SLUG}}/ by prizmkit-code-review.
|
|
7
7
|
Report: verdict (PASS/NEEDS_FIXES), number of rounds, findings fixed/rejected."
|
|
@@ -30,9 +30,9 @@ Infer what needs to be done from the feature context above and follow the standa
|
|
|
30
30
|
|
|
31
31
|
3. **Implement**: Run `/prizmkit-implement` with `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` to execute the plan using TDD (write tests first, then implement).
|
|
32
32
|
|
|
33
|
-
4. **
|
|
33
|
+
4. **Review**: Run `/prizmkit-code-review` with `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` to review and auto-fix changes against the spec (internal review-fix loop, max 3 rounds).
|
|
34
34
|
|
|
35
|
-
5. **
|
|
35
|
+
5. **Test**: Run `/prizmkit-test scope=this-change artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` to generate and verify tests only for this feature's changed scope after review. Do not use bugfix/refactor artifact directories for this gate.
|
|
36
36
|
|
|
37
37
|
6. **Retrospective**: Run `/prizmkit-retrospective` to sync `.prizmkit/prizm-docs/` with code changes.
|
|
38
38
|
|
|
@@ -145,9 +145,9 @@ You know this project's tech stack. Identify ALL test commands that apply (e.g.,
|
|
|
145
145
|
**3b.** Run `/prizmkit-implement` — this handles the full implementation cycle:
|
|
146
146
|
- Reads plan.md Tasks section from `.prizmkit/specs/{{FEATURE_SLUG}}/`
|
|
147
147
|
- Reads context from `context-snapshot.md` (Prizm docs, TRAPS, file manifest)
|
|
148
|
-
- Implements task-by-task
|
|
148
|
+
- Implements task-by-task, marking each `[x]` immediately
|
|
149
149
|
- Creates/updates L2 `.prizm` docs when creating new modules or significantly modifying existing ones — AI selectively decides which modules warrant L2 based on complexity and importance
|
|
150
|
-
-
|
|
150
|
+
- Defers scoped/full test execution until after review
|
|
151
151
|
- Writes '## Implementation Log' to `context-snapshot.md`
|
|
152
152
|
|
|
153
153
|
**3b-checkpoint.** Context management — if plan.md has more than 5 tasks, update durable checkpoints/artifacts after every 3 tasks, minimize output, and continue. If context overflow occurs in a headless run, the runner starts a fresh continuation session from those checkpoints and artifacts. In an interactive Claude Code session operated manually outside the headless pipeline, `/compact` may be used as an optional convenience only.
|
|
@@ -155,10 +155,9 @@ You know this project's tech stack. Identify ALL test commands that apply (e.g.,
|
|
|
155
155
|
**3c.** After implement completes, verify:
|
|
156
156
|
1. All tasks in plan.md are `[x]`
|
|
157
157
|
2. **Verification Gate Self-Check**: for each acceptance criterion, write one line of evidence (test name / file:line / fixture path). Any gate without evidence is BLOCKED, not success.
|
|
158
|
-
3. **Layered test strategy**:
|
|
159
|
-
4.
|
|
160
|
-
5.
|
|
161
|
-
6. If any criterion is not met, fix it now using the convergence-based recovery loop below
|
|
158
|
+
3. **Layered test strategy**: do not run mandatory periodic/full-suite tests during implementation. Use only tiny targeted checks when needed for local debugging; the scoped feature test gate runs after review.
|
|
159
|
+
4. Verify each acceptance criterion from Section 1 of context-snapshot.md is met — check mentally, do NOT re-read files you already wrote
|
|
160
|
+
5. If any criterion is not met, fix it now using the convergence-based recovery loop below
|
|
162
161
|
|
|
163
162
|
**CP-2**: All acceptance criteria met, all tests pass.
|
|
164
163
|
|
|
@@ -177,7 +176,7 @@ When tests fail, use convergence recovery — keep fixing while progress is bein
|
|
|
177
176
|
|
|
178
177
|
### Scoped Feature Test Gate — PrizmKit Test
|
|
179
178
|
|
|
180
|
-
Run `/prizmkit-test scope=this-change artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` after
|
|
179
|
+
Run `/prizmkit-test scope=this-change artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` after review and before commit.
|
|
181
180
|
|
|
182
181
|
Before invoking the skill, create `.prizmkit/specs/{{FEATURE_SLUG}}/.prizmkit-test-started` so stale reports are ignored.
|
|
183
182
|
|
|
@@ -302,7 +302,7 @@ If GATE:MISSING — send message to Dev (re-spawn if needed): "Write the '## Imp
|
|
|
302
302
|
|
|
303
303
|
### Scoped Feature Test Gate — PrizmKit Test
|
|
304
304
|
|
|
305
|
-
Run `/prizmkit-test scope=this-change artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` after
|
|
305
|
+
Run `/prizmkit-test scope=this-change artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` after review and before commit.
|
|
306
306
|
|
|
307
307
|
Before invoking the skill, create `.prizmkit/specs/{{FEATURE_SLUG}}/.prizmkit-test-started` so stale reports are ignored.
|
|
308
308
|
|
|
@@ -320,7 +320,7 @@ All tasks `[x]`, tests pass.
|
|
|
320
320
|
|
|
321
321
|
### Scoped Feature Test Gate — PrizmKit Test
|
|
322
322
|
|
|
323
|
-
Run `/prizmkit-test scope=this-change artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` after
|
|
323
|
+
Run `/prizmkit-test scope=this-change artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` after review and before commit.
|
|
324
324
|
|
|
325
325
|
Before invoking the skill, create `.prizmkit/specs/{{FEATURE_SLUG}}/.prizmkit-test-started` so stale reports are ignored.
|
|
326
326
|
|
|
@@ -19,15 +19,15 @@ grep -c '^\- \[ \]' .prizmkit/specs/{{FEATURE_SLUG}}/plan.md 2>/dev/null || true
|
|
|
19
19
|
**3b.** Run `/prizmkit-implement` directly — you (the orchestrator) execute the full cycle:
|
|
20
20
|
- Read plan.md Tasks from `.prizmkit/specs/{{FEATURE_SLUG}}/`
|
|
21
21
|
- Use context-snapshot.md Section 4 File Manifest for targeted reads — do NOT re-read files already summarized there
|
|
22
|
-
-
|
|
22
|
+
- Implements task-by-task, marking each `[x]` immediately
|
|
23
23
|
- Creates/updates L2 `.prizm` docs when creating new modules
|
|
24
|
-
-
|
|
24
|
+
- Defers scoped/full test execution until after the code-review loop completes
|
|
25
25
|
- If plan.md has >5 tasks: update checkpoints after every 3 tasks
|
|
26
26
|
|
|
27
|
-
**3c.
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
27
|
+
**3c. Focused checks only (no test gate here)**:
|
|
28
|
+
- During implementation, do not run mandatory periodic or full-suite tests.
|
|
29
|
+
- If a tiny targeted check is needed to understand a local failure, run only the smallest relevant command and capture output to `/tmp/test-out.txt`.
|
|
30
|
+
- The scoped feature test gate runs after code review; do not declare implementation success based on an early full-suite run.
|
|
31
31
|
|
|
32
32
|
**3d. Verification Gate Self-Check (BLOCKING — before declaring implement done)**:
|
|
33
33
|
For each Verification Gate from the Task Contract, write one evidence line to Implementation Log:
|
|
@@ -45,7 +45,7 @@ For each Verification Gate from the Task Contract, write one evidence line to Im
|
|
|
45
45
|
- Gate Evidence section
|
|
46
46
|
- unresolved blockers
|
|
47
47
|
|
|
48
|
-
**CP-2**: All tasks `[x]`, Implementation Log written with Gate Evidence,
|
|
48
|
+
**CP-2**: All tasks `[x]`, Implementation Log written with Gate Evidence, and blocked gates documented in `failure-log.md`. Test execution is deferred until after code review.
|
|
49
49
|
|
|
50
50
|
**Checkpoint update**:
|
|
51
51
|
```bash
|
|
@@ -20,15 +20,15 @@ Save pre-existing failing tests as `BASELINE_FAILURES`.
|
|
|
20
20
|
**3b.** Run `/prizmkit-implement` directly — you (the orchestrator) execute the full cycle:
|
|
21
21
|
- Read plan.md Tasks from `.prizmkit/specs/{{FEATURE_SLUG}}/`
|
|
22
22
|
- Use context-snapshot.md Section 4 File Manifest for targeted reads — do NOT re-read files already summarized there
|
|
23
|
-
-
|
|
23
|
+
- Implements task-by-task, marking each `[x]` immediately
|
|
24
24
|
- Creates/updates L2 `.prizm` docs when creating new modules
|
|
25
|
-
-
|
|
25
|
+
- Defers scoped/full test execution until after the code-review loop completes
|
|
26
26
|
- If plan.md has >5 tasks: update checkpoints after every 3 tasks
|
|
27
27
|
|
|
28
|
-
**3c.
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
28
|
+
**3c. Focused checks only (no test gate here)**:
|
|
29
|
+
- During implementation, do not run mandatory periodic or full-suite tests.
|
|
30
|
+
- If a tiny targeted check is needed to understand a local failure, run only the smallest relevant command and capture output to `/tmp/test-out.txt`.
|
|
31
|
+
- The scoped feature test gate runs after code review; do not declare implementation success based on an early full-suite run.
|
|
32
32
|
|
|
33
33
|
**3d. Verification Gate Self-Check (BLOCKING — before declaring implement done)**:
|
|
34
34
|
For each Verification Gate from the Task Contract, write one evidence line to Implementation Log:
|
|
@@ -46,7 +46,7 @@ For each Verification Gate from the Task Contract, write one evidence line to Im
|
|
|
46
46
|
- Gate Evidence section
|
|
47
47
|
- unresolved blockers
|
|
48
48
|
|
|
49
|
-
**CP-2**: All tasks `[x]`, Implementation Log written with Gate Evidence,
|
|
49
|
+
**CP-2**: All tasks `[x]`, Implementation Log written with Gate Evidence, and blocked gates documented in `failure-log.md`. Test execution is deferred until after code review.
|
|
50
50
|
|
|
51
51
|
**Checkpoint update**:
|
|
52
52
|
```bash
|
|
@@ -1,33 +1,24 @@
|
|
|
1
|
-
### Implement
|
|
1
|
+
### Implement
|
|
2
2
|
|
|
3
3
|
**Protocol references**:
|
|
4
4
|
- Follow Context Budget Rules §8 for scaffold/generated files.
|
|
5
5
|
- Follow Context Budget Rules §9 before package install/build commands that resolve dependencies.
|
|
6
6
|
- Follow Context Budget Rules §10 after build/compile commands.
|
|
7
7
|
|
|
8
|
-
**3a.**
|
|
9
|
-
|
|
10
|
-
You know this project's tech stack. Identify ALL test commands that apply (e.g., `go test ./...`, `npm test`, `cargo test`, `pytest`, `make test`, etc.). Record them as `TEST_CMDS`. Then record baseline:
|
|
11
|
-
```bash
|
|
12
|
-
# Run each test command, capture output
|
|
13
|
-
($TEST_CMD) 2>&1 | tee /tmp/test-baseline.txt | tail -20
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
**3b.** Run `/prizmkit-implement` — this handles the full implementation cycle:
|
|
8
|
+
**3a.** Run `/prizmkit-implement` — this handles the implementation cycle:
|
|
17
9
|
- Reads plan.md Tasks section from `.prizmkit/specs/{{FEATURE_SLUG}}/`
|
|
18
10
|
- Reads context from `context-snapshot.md` (Prizm docs, TRAPS, file manifest)
|
|
19
|
-
- Implements task-by-task
|
|
11
|
+
- Implements task-by-task, marking each `[x]` immediately
|
|
20
12
|
- Creates/updates L2 `.prizm` docs when creating new modules or significantly modifying existing ones — AI selectively decides which modules warrant L2 based on complexity and importance
|
|
21
|
-
-
|
|
13
|
+
- Does not run the scoped/full test gate; testing happens after code review in the PrizmKit Test phase
|
|
22
14
|
- Writes '## Implementation Log' to `context-snapshot.md`
|
|
23
15
|
|
|
24
|
-
**
|
|
16
|
+
**3b.** After implement completes, verify:
|
|
25
17
|
1. All tasks in plan.md are `[x]`
|
|
26
|
-
2.
|
|
27
|
-
3.
|
|
28
|
-
4. If any gate is unmet or blocked, follow the Test Failure Recovery Protocol
|
|
18
|
+
2. Verify each Verification Gate from the Task Contract using code/file evidence already in context — do NOT re-read files you already wrote
|
|
19
|
+
3. If any gate is unmet or blocked, write `failure-log.md` and stop for recovery
|
|
29
20
|
|
|
30
|
-
**CP-2**: Implementation may proceed only when all tasks are `[x]` and
|
|
21
|
+
**CP-2**: Implementation may proceed only when all tasks are `[x]` and blocked gates are documented in `failure-log.md`. Test execution is deferred until after code review.
|
|
31
22
|
|
|
32
23
|
|
|
33
24
|
**Checkpoint update**: Run the update script to set step `prizmkit-implement` to `"completed"`:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
### Scoped Feature Test Gate — PrizmKit Test
|
|
2
2
|
|
|
3
|
-
**Goal**:
|
|
3
|
+
**Goal**: After code review completes, generate and verify tests for this feature's changed scope without embedding the full gate implementation in this prompt.
|
|
4
4
|
|
|
5
5
|
Create a start marker immediately before invoking the skill so the gate can reject stale reports from older sessions:
|
|
6
6
|
|
|
@@ -34,6 +34,6 @@ python3 "$PIPELINE_DIR/scripts/prizmkit-test-gate.py" check \
|
|
|
34
34
|
|
|
35
35
|
Gate outcomes:
|
|
36
36
|
- `GATE:PASS` → append the report path and a 3-5 bullet summary to `context-snapshot.md` under `## PrizmKit Test Gate`, then proceed to the next checkpoint step. The gate script already wrote `test-report-path.txt` and marked `prizmkit-test` completed.
|
|
37
|
-
- `GATE:FAIL` with `NEEDS_FIXES` → read the report's `In-Scope Failures`, fix the feature implementation or generated tests within this feature scope, rerun `/prizmkit-test`, and repeat the gate check. Do not proceed to
|
|
37
|
+
- `GATE:FAIL` with `NEEDS_FIXES` → read the report's `In-Scope Failures`, fix the feature implementation or generated tests within this feature scope, rerun `/prizmkit-test`, and repeat the gate check. Do not proceed to retrospective/commit while the verdict is not `PASS`.
|
|
38
38
|
- `GATE:FAIL` with `BLOCKED`, wrong mode, wrong artifact dir, unreadable report, stale scope, boundary failure, validator failure, or checkpoint update failure → write `.prizmkit/specs/{{FEATURE_SLUG}}/failure-log.md` with the scoped test failure and stop for recovery.
|
|
39
39
|
- `GATE:MISSING` → perform one bounded status check: inspect `/prizmkit-test` output and `.prizmkit/test/` for a report directory. If no current-run report exists, write `failure-log.md` and stop for recovery.
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
### Review — Code Review
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Before invoking review, read `.prizmkit/specs/{{FEATURE_SLUG}}/test-report-path.txt`, read the referenced `/prizmkit-test` report, and confirm the summary in `context-snapshot.md` under `## PrizmKit Test Gate` matches it. The code-review skill must receive this context through `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` and the Reviewer must assess generated/updated test quality, `In-Scope Failures`, `Baseline Failures`, `Out of Scope Gaps`, boundary coverage if present, and the scoped report verdict.
|
|
3
|
+
Review the completed implementation before the scoped feature test gate runs. Use `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` so the code-review skill can read spec, plan, context snapshot, implementation log, and current diff artifacts. Do not require or read a prior `/prizmkit-test` report here; test generation and test-fix looping happen in the next phase.
|
|
6
4
|
|
|
7
5
|
Run `/prizmkit-code-review` with `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/`.
|
|
8
6
|
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
### Review — Code Review
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Before invoking review, read `.prizmkit/specs/{{FEATURE_SLUG}}/test-report-path.txt`, read the referenced `/prizmkit-test` report, and confirm the summary in `context-snapshot.md` under `## PrizmKit Test Gate` matches it. The code-review skill must receive this context through `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` and the Reviewer must assess generated/updated test quality, `In-Scope Failures`, `Baseline Failures`, `Out of Scope Gaps`, boundary coverage if present, and the scoped report verdict.
|
|
3
|
+
Review the completed implementation before the scoped feature test gate runs. Use `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` so the code-review skill can read spec, plan, context snapshot, implementation log, and current diff artifacts. Do not require or read a prior `/prizmkit-test` report here; test generation and test-fix looping happen in the next phase.
|
|
6
4
|
|
|
7
5
|
Run `/prizmkit-code-review` with `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/`.
|
|
8
6
|
|
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
### Phase 0:
|
|
1
|
+
### Phase 0: Detect Test Commands
|
|
2
2
|
|
|
3
3
|
**Step 1 — Detect test commands**: You know this project's tech stack. Identify ALL test commands that apply (e.g., `go test ./...`, `npm test`, `cargo test`, `pytest`, `make test`, etc.). Record them as `TEST_CMDS`.
|
|
4
4
|
|
|
5
|
-
**Step 2 —
|
|
6
|
-
```bash
|
|
7
|
-
# Run each test command, capture output
|
|
8
|
-
($TEST_CMD) 2>&1 | tee /tmp/test-baseline.txt | tail -20
|
|
9
|
-
```
|
|
10
|
-
Save the list of **pre-existing failing tests** (if any) as `BASELINE_FAILURES`. These are known failures that existed before this session — Dev must NOT be blamed for them, but must list them in COMPLETION_SIGNAL.
|
|
5
|
+
**Step 2 — Do not run tests yet**: This phase only records the commands that will be used by the post-review PrizmKit Test gate. Do not run baseline, periodic, or full-suite tests here; test execution happens after code review.
|
|
11
6
|
|
|
12
|
-
> **Test Output Rule**:
|
|
7
|
+
> **Test Output Rule**: When the post-review gate runs, capture test output to a temp file (`tee /tmp/test-out.txt`). Then grep the file instead of re-running the suite.
|
|
13
8
|
|
|
14
9
|
|
|
15
10
|
**Checkpoint update**: Run the update script to set step `test-baseline` to `"completed"`:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## Test Failure Recovery Protocol
|
|
2
2
|
|
|
3
|
-
Use this protocol whenever
|
|
3
|
+
Use this protocol whenever the post-review PrizmKit Test gate fails. Its purpose is to distinguish tolerated pre-existing failures from blockers introduced by this session.
|
|
4
4
|
|
|
5
5
|
### Failure Classes
|
|
6
6
|
|
|
@@ -25,7 +25,7 @@ After **6 total test-fix rounds**, STOP trial-and-error. Write failure-log listi
|
|
|
25
25
|
|
|
26
26
|
### Success Rule
|
|
27
27
|
|
|
28
|
-
Proceed to
|
|
28
|
+
Proceed to retrospective/commit only when:
|
|
29
29
|
|
|
30
30
|
1. all new regressions are fixed;
|
|
31
31
|
2. baseline failures are documented;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## Test Failure Recovery Protocol
|
|
2
2
|
|
|
3
|
-
Use this protocol whenever
|
|
3
|
+
Use this protocol whenever the post-review PrizmKit Test gate fails. Its purpose is to distinguish tolerated pre-existing failures from blockers introduced by this session.
|
|
4
4
|
|
|
5
5
|
### Failure Classes
|
|
6
6
|
|
|
@@ -406,7 +406,7 @@ class TestProcessModeBlocks:
|
|
|
406
406
|
# ---------------------------------------------------------------------------
|
|
407
407
|
|
|
408
408
|
class TestScopedFeatureTestGate:
|
|
409
|
-
def
|
|
409
|
+
def test_standard_sections_insert_prizmkit_test_after_review(self):
|
|
410
410
|
sections_dir = Path(__file__).resolve().parents[1] / "templates" / "sections"
|
|
411
411
|
sections = assemble_sections(
|
|
412
412
|
"standard",
|
|
@@ -419,7 +419,7 @@ class TestScopedFeatureTestGate:
|
|
|
419
419
|
names = [name for name, _ in sections]
|
|
420
420
|
|
|
421
421
|
assert "phase-prizmkit-test" in names
|
|
422
|
-
assert names.index("phase-implement") < names.index("phase-
|
|
422
|
+
assert names.index("phase-implement") < names.index("phase-review") < names.index("phase-prizmkit-test")
|
|
423
423
|
|
|
424
424
|
rendered = "\n".join(content for _, content in sections)
|
|
425
425
|
assert "prizmkit-test-gate.py" in rendered
|
|
@@ -430,8 +430,12 @@ class TestScopedFeatureTestGate:
|
|
|
430
430
|
assert ".prizmkit/refactor/" in rendered # explicitly forbidden in the gate wording
|
|
431
431
|
assert "latest_test_report=$(python3" not in rendered
|
|
432
432
|
assert "boundary_columns_ok=$(awk" not in rendered
|
|
433
|
+
assert "Runs tests using `TEST_CMD` after each task" not in rendered
|
|
434
|
+
assert "Run the full test suite" not in rendered
|
|
435
|
+
assert "before code review" not in rendered
|
|
436
|
+
assert "Precondition: the `prizmkit-test` checkpoint is completed" not in rendered
|
|
433
437
|
|
|
434
|
-
def
|
|
438
|
+
def test_lite_sections_include_review_before_prizmkit_test_and_commit(self):
|
|
435
439
|
sections_dir = Path(__file__).resolve().parents[1] / "templates" / "sections"
|
|
436
440
|
sections = assemble_sections(
|
|
437
441
|
"lite",
|
|
@@ -444,10 +448,10 @@ class TestScopedFeatureTestGate:
|
|
|
444
448
|
names = [name for name, _ in sections]
|
|
445
449
|
|
|
446
450
|
assert "phase-prizmkit-test" in names
|
|
447
|
-
assert "phase-review"
|
|
448
|
-
assert names.index("phase-implement") < names.index("phase-prizmkit-test") < names.index("phase-commit")
|
|
451
|
+
assert "phase-review" in names
|
|
452
|
+
assert names.index("phase-implement") < names.index("phase-review") < names.index("phase-prizmkit-test") < names.index("phase-commit")
|
|
449
453
|
|
|
450
|
-
def
|
|
454
|
+
def test_checkpoint_orders_prizmkit_test_after_review(self):
|
|
451
455
|
sections_dir = Path(__file__).resolve().parents[1] / "templates" / "sections"
|
|
452
456
|
sections = assemble_sections(
|
|
453
457
|
"standard",
|
|
@@ -468,7 +472,7 @@ class TestScopedFeatureTestGate:
|
|
|
468
472
|
)
|
|
469
473
|
skills = [step["skill"] for step in checkpoint["steps"]]
|
|
470
474
|
|
|
471
|
-
assert skills.index("prizmkit-implement") < skills.index("prizmkit-
|
|
475
|
+
assert skills.index("prizmkit-implement") < skills.index("prizmkit-code-review") < skills.index("prizmkit-test")
|
|
472
476
|
test_step = next(step for step in checkpoint["steps"] if step["skill"] == "prizmkit-test")
|
|
473
477
|
assert test_step["required_artifacts"] == [
|
|
474
478
|
".prizmkit/specs/123-scope-test/test-report-path.txt",
|
|
@@ -291,7 +291,7 @@ def test_runner_models_parse_legacy_args_and_environment(tmp_path):
|
|
|
291
291
|
invocation = parse_invocation(
|
|
292
292
|
family,
|
|
293
293
|
"run",
|
|
294
|
-
("custom-list.json", "F-2", "--features", "F-003:F-001", "--max-infra-retries=5", "--mode", "full", "--critic"),
|
|
294
|
+
("custom-list.json", "F-2", "--features", "F-003:F-001", "--max-retries", "7", "--max-infra-retries=5", "--mode", "full", "--critic"),
|
|
295
295
|
)
|
|
296
296
|
env = RunnerEnvironment.from_env(
|
|
297
297
|
{
|
|
@@ -300,6 +300,7 @@ def test_runner_models_parse_legacy_args_and_environment(tmp_path):
|
|
|
300
300
|
"DEV_BRANCH": "dev/custom",
|
|
301
301
|
"VERBOSE": "1",
|
|
302
302
|
"STALE_KILL_THRESHOLD": "9",
|
|
303
|
+
"MAX_RETRIES": "9",
|
|
303
304
|
"STRICT_BEHAVIOR_CHECK": "false",
|
|
304
305
|
}
|
|
305
306
|
)
|
|
@@ -307,6 +308,7 @@ def test_runner_models_parse_legacy_args_and_environment(tmp_path):
|
|
|
307
308
|
assert invocation.list_path == Path("custom-list.json")
|
|
308
309
|
assert invocation.item_id == "F-002"
|
|
309
310
|
assert invocation.item_filter == "F-003:F-001"
|
|
311
|
+
assert invocation.max_retries == 7
|
|
310
312
|
assert invocation.max_infra_retries == 5
|
|
311
313
|
assert invocation.mode == "full"
|
|
312
314
|
assert invocation.critic is True
|
|
@@ -314,6 +316,7 @@ def test_runner_models_parse_legacy_args_and_environment(tmp_path):
|
|
|
314
316
|
assert env.auto_push is True
|
|
315
317
|
assert env.dev_branch == "dev/custom"
|
|
316
318
|
assert env.stale_kill_threshold_seconds == 9
|
|
319
|
+
assert env.max_retries == 9
|
|
317
320
|
assert env.strict_behavior_check is False
|
|
318
321
|
|
|
319
322
|
|
|
@@ -355,6 +358,91 @@ def test_status_bridge_preserves_feature_filter_selection(tmp_path):
|
|
|
355
358
|
assert update.data["context_overflow_count"] == 1
|
|
356
359
|
|
|
357
360
|
|
|
361
|
+
def test_status_bridge_passes_max_retries_from_invocation_or_environment(monkeypatch, tmp_path):
|
|
362
|
+
import subprocess
|
|
363
|
+
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
364
|
+
from prizmkit_runtime.runner_status import status_text
|
|
365
|
+
|
|
366
|
+
paths = _make_paths(tmp_path)
|
|
367
|
+
family = family_for("feature", paths)
|
|
368
|
+
explicit = parse_invocation(family, "run", ("--max-retries", "8"))
|
|
369
|
+
explicit = explicit.__class__(**{**explicit.__dict__, "list_path": paths.feature_plan})
|
|
370
|
+
fallback = parse_invocation(family, "run", ())
|
|
371
|
+
fallback = fallback.__class__(**{**fallback.__dict__, "list_path": paths.feature_plan})
|
|
372
|
+
calls = []
|
|
373
|
+
|
|
374
|
+
def fake_run(command, **kwargs):
|
|
375
|
+
calls.append(tuple(command))
|
|
376
|
+
return subprocess.CompletedProcess(command, 0, stdout="PIPELINE_COMPLETE\n", stderr="")
|
|
377
|
+
|
|
378
|
+
monkeypatch.setattr(subprocess, "run", fake_run)
|
|
379
|
+
monkeypatch.setenv("MAX_RETRIES", "6")
|
|
380
|
+
|
|
381
|
+
status_text(family, explicit, paths.project_root)
|
|
382
|
+
status_text(family, fallback, paths.project_root)
|
|
383
|
+
|
|
384
|
+
first = calls[0]
|
|
385
|
+
second = calls[1]
|
|
386
|
+
assert first[first.index("--max-retries") + 1] == "8"
|
|
387
|
+
assert second[second.index("--max-retries") + 1] == "6"
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def test_stop_on_failure_does_not_stop_on_retryable_pending(monkeypatch, tmp_path):
|
|
391
|
+
from prizmkit_runtime import runners
|
|
392
|
+
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
393
|
+
|
|
394
|
+
paths = _make_paths(tmp_path)
|
|
395
|
+
family = family_for("feature", paths)
|
|
396
|
+
invocation = parse_invocation(family, "run", ())
|
|
397
|
+
invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
|
|
398
|
+
queue = [
|
|
399
|
+
SimpleNamespace(ok=True, stdout=json.dumps({"feature_id": "F-001"}), data={"feature_id": "F-001"}, text=""),
|
|
400
|
+
SimpleNamespace(ok=True, stdout=json.dumps({"feature_id": "F-002"}), data={"feature_id": "F-002"}, text=""),
|
|
401
|
+
SimpleNamespace(ok=True, stdout="PIPELINE_COMPLETE\n", data=None, text="PIPELINE_COMPLETE"),
|
|
402
|
+
]
|
|
403
|
+
processed = []
|
|
404
|
+
|
|
405
|
+
monkeypatch.setenv("STOP_ON_FAILURE", "1")
|
|
406
|
+
monkeypatch.setattr(runners, "get_next", lambda *args, **kwargs: queue.pop(0))
|
|
407
|
+
|
|
408
|
+
def fake_process(_family, _invocation, item_id, _paths, *, initial_metadata):
|
|
409
|
+
processed.append(item_id)
|
|
410
|
+
return "pending" if item_id == "F-001" else "completed"
|
|
411
|
+
|
|
412
|
+
monkeypatch.setattr(runners, "_process_item", fake_process)
|
|
413
|
+
|
|
414
|
+
result = runners._run_pipeline(family, invocation, paths)
|
|
415
|
+
|
|
416
|
+
assert processed == ["F-001", "F-002"]
|
|
417
|
+
assert result.completed is True
|
|
418
|
+
assert result.stopped_reason == "pipeline_complete"
|
|
419
|
+
assert result.details == ("F-001:pending", "F-002:completed")
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def test_stop_on_failure_still_stops_on_terminal_status(monkeypatch, tmp_path):
|
|
423
|
+
from prizmkit_runtime import runners
|
|
424
|
+
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
425
|
+
|
|
426
|
+
paths = _make_paths(tmp_path)
|
|
427
|
+
family = family_for("feature", paths)
|
|
428
|
+
invocation = parse_invocation(family, "run", ())
|
|
429
|
+
invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
|
|
430
|
+
|
|
431
|
+
monkeypatch.setenv("STOP_ON_FAILURE", "1")
|
|
432
|
+
monkeypatch.setattr(
|
|
433
|
+
runners,
|
|
434
|
+
"get_next",
|
|
435
|
+
lambda *args, **kwargs: SimpleNamespace(ok=True, stdout=json.dumps({"feature_id": "F-001"}), data={"feature_id": "F-001"}, text=""),
|
|
436
|
+
)
|
|
437
|
+
monkeypatch.setattr(runners, "_process_item", lambda *args, **kwargs: "failed")
|
|
438
|
+
|
|
439
|
+
result = runners._run_pipeline(family, invocation, paths)
|
|
440
|
+
|
|
441
|
+
assert result.completed is False
|
|
442
|
+
assert result.stopped_reason == "stop_on_failure"
|
|
443
|
+
assert result.last_status == "failed"
|
|
444
|
+
|
|
445
|
+
|
|
358
446
|
def test_prompt_generation_result_preserves_per_item_model(monkeypatch, tmp_path):
|
|
359
447
|
from prizmkit_runtime.runner_models import SessionPaths, family_for, parse_invocation
|
|
360
448
|
from prizmkit_runtime.runner_prompts import generate_prompt
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
confirmTeamMode,
|
|
55
55
|
confirmPipeline,
|
|
56
56
|
} from './prompts.js';
|
|
57
|
-
import {
|
|
57
|
+
import { isKnownPlatform, platformLabel, projectMemoryFile, resolvePayloadPlatforms } from './platforms.js';
|
|
58
58
|
import { normalizeRuntime, runtimeLabel } from './runtimes.js';
|
|
59
59
|
|
|
60
60
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -490,9 +490,9 @@ export async function runConfig(directory, options = {}) {
|
|
|
490
490
|
}
|
|
491
491
|
|
|
492
492
|
// Warn about destructive changes
|
|
493
|
-
if (newConfig.platform !== currentConfig.platform) {
|
|
494
|
-
const oldPlatforms =
|
|
495
|
-
const newPlatforms =
|
|
493
|
+
if (newConfig.platform !== currentConfig.platform || newConfig.aiCli !== currentConfig.aiCli) {
|
|
494
|
+
const oldPlatforms = resolvePayloadPlatforms(currentConfig.platform, currentConfig.aiCli);
|
|
495
|
+
const newPlatforms = resolvePayloadPlatforms(newConfig.platform, newConfig.aiCli);
|
|
496
496
|
const dropping = oldPlatforms.filter(p => !newPlatforms.includes(p));
|
|
497
497
|
if (dropping.length) {
|
|
498
498
|
console.log(chalk.yellow(`\n ⚠ 将移除 ${dropping.map(platformLabel).join(', ')} 的 PrizmKit 文件`));
|
|
@@ -521,8 +521,8 @@ export async function runConfig(directory, options = {}) {
|
|
|
521
521
|
|
|
522
522
|
console.log(chalk.bold('\n 应用配置变更...\n'));
|
|
523
523
|
|
|
524
|
-
const oldPlatforms =
|
|
525
|
-
const newPlatforms =
|
|
524
|
+
const oldPlatforms = resolvePayloadPlatforms(currentConfig.platform, currentConfig.aiCli);
|
|
525
|
+
const newPlatforms = resolvePayloadPlatforms(newConfig.platform, newConfig.aiCli);
|
|
526
526
|
const platformsToRemove = oldPlatforms.filter(p => !newPlatforms.includes(p));
|
|
527
527
|
const platformsToAdd = newPlatforms.filter(p => !oldPlatforms.includes(p));
|
|
528
528
|
const platformsToUpdate = newPlatforms.filter(p => oldPlatforms.includes(p));
|
|
@@ -554,7 +554,7 @@ export async function runConfig(directory, options = {}) {
|
|
|
554
554
|
version: pkg.version, platform: newConfig.platform, suite: newConfig.suite,
|
|
555
555
|
skills: newSkillList, agents: newAgentFiles, rules: newRuleFiles,
|
|
556
556
|
pipeline: newPipelineFiles, team: newConfig.team, aiCli: newConfig.aiCli,
|
|
557
|
-
runtime: newConfig.runtime, rulesPreset: newConfig.rules, extras: oldManifest?.files?.extras || [],
|
|
557
|
+
runtime: newConfig.runtime, rulesPreset: newConfig.rules, extras: oldManifest?.files?.extras || [], payloadPlatforms: newPlatforms,
|
|
558
558
|
});
|
|
559
559
|
const diff = diffManifest(oldManifest, newTempManifest);
|
|
560
560
|
|
|
@@ -580,6 +580,7 @@ export async function runConfig(directory, options = {}) {
|
|
|
580
580
|
|| newConfig.runtime !== currentConfig.runtime
|
|
581
581
|
|| newConfig.suite !== currentConfig.suite
|
|
582
582
|
|| newConfig.rules !== currentConfig.rules
|
|
583
|
+
|| newConfig.aiCli !== currentConfig.aiCli
|
|
583
584
|
|| newConfig.team !== currentConfig.team;
|
|
584
585
|
|
|
585
586
|
for (const p of allTargetPlatforms) {
|
|
@@ -675,6 +676,7 @@ export async function runConfig(directory, options = {}) {
|
|
|
675
676
|
team: newConfig.team,
|
|
676
677
|
aiCli: newConfig.aiCli,
|
|
677
678
|
rulesPreset: newConfig.rules,
|
|
679
|
+
payloadPlatforms: newPlatforms,
|
|
678
680
|
extras: oldManifest?.files?.extras || [],
|
|
679
681
|
});
|
|
680
682
|
|
package/src/external-skills.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { execSync } from 'node:child_process';
|
|
15
15
|
import path from 'path';
|
|
16
16
|
import fs from 'fs-extra';
|
|
17
|
-
import {
|
|
17
|
+
import { resolvePayloadPlatforms } from './platforms.js';
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* @param {Object} skill - Skill definition from external_skills.known
|
|
@@ -23,9 +23,9 @@ import { expandPlatforms } from './platforms.js';
|
|
|
23
23
|
* skill.installAll {boolean} - If true, install all skills from the repo
|
|
24
24
|
* @param {string} platform - 'claude' | 'codebuddy' | 'codex' | 'all' ('both' supported only for legacy manifests)
|
|
25
25
|
* @param {string} projectRoot - Target project root
|
|
26
|
-
* @param {
|
|
26
|
+
* @param {string} [aiCli] - optional AI CLI command that may require an additional payload platform
|
|
27
27
|
*/
|
|
28
|
-
export async function installExternalSkill(skill, platform, projectRoot, dryRun) {
|
|
28
|
+
export async function installExternalSkill(skill, platform, projectRoot, dryRun, aiCli = '') {
|
|
29
29
|
if (!skill.repo) {
|
|
30
30
|
throw new Error(`Skill "${skill.name}" has no repo URL defined`);
|
|
31
31
|
}
|
|
@@ -40,7 +40,7 @@ export async function installExternalSkill(skill, platform, projectRoot, dryRun)
|
|
|
40
40
|
if (skill.installAll) {
|
|
41
41
|
console.log(` [dry-run] → all skills from ${skill.repo}`);
|
|
42
42
|
} else {
|
|
43
|
-
const targets = getTargetDirs(platform, projectRoot)
|
|
43
|
+
const targets = getTargetDirs(platform, projectRoot, aiCli)
|
|
44
44
|
.map(d => path.relative(projectRoot, path.join(d, skill.name, 'SKILL.md')));
|
|
45
45
|
for (const t of targets) console.log(` [dry-run] → ${t}`);
|
|
46
46
|
}
|
|
@@ -61,7 +61,7 @@ export async function installExternalSkill(skill, platform, projectRoot, dryRun)
|
|
|
61
61
|
.filter(d => d.isDirectory())
|
|
62
62
|
.map(d => d.name);
|
|
63
63
|
|
|
64
|
-
for (const targetDir of getTargetDirs(platform, projectRoot)) {
|
|
64
|
+
for (const targetDir of getTargetDirs(platform, projectRoot, aiCli)) {
|
|
65
65
|
for (const skillName of skillDirs) {
|
|
66
66
|
const srcDir = path.join(agentsSkillsDir, skillName);
|
|
67
67
|
const destDir = path.join(targetDir, skillName);
|
|
@@ -81,7 +81,7 @@ export async function installExternalSkill(skill, platform, projectRoot, dryRun)
|
|
|
81
81
|
|
|
82
82
|
const content = await fs.readFile(canonicalSkillMd, 'utf8');
|
|
83
83
|
|
|
84
|
-
for (const targetDir of getTargetDirs(platform, projectRoot)) {
|
|
84
|
+
for (const targetDir of getTargetDirs(platform, projectRoot, aiCli)) {
|
|
85
85
|
const skillDir = path.join(targetDir, skill.name);
|
|
86
86
|
if (path.resolve(skillDir) === path.resolve(path.dirname(canonicalSkillMd))) {
|
|
87
87
|
continue;
|
|
@@ -94,7 +94,7 @@ export async function installExternalSkill(skill, platform, projectRoot, dryRun)
|
|
|
94
94
|
|
|
95
95
|
// Clean up npx skills artifacts after copying. Preserve .agents/skills when
|
|
96
96
|
// Codex is selected because it is the official repository skill location.
|
|
97
|
-
if (!
|
|
97
|
+
if (!resolvePayloadPlatforms(platform, aiCli).includes('codex')) {
|
|
98
98
|
await fs.remove(path.join(projectRoot, '.agents')).catch(() => {});
|
|
99
99
|
}
|
|
100
100
|
await fs.remove(path.join(projectRoot, 'skills-lock.json')).catch(() => {});
|
|
@@ -104,15 +104,15 @@ export async function installExternalSkill(skill, platform, projectRoot, dryRun)
|
|
|
104
104
|
* Remove all artifacts created by `npx skills` that are not needed by PrizmKit.
|
|
105
105
|
* Call this once after all external skills have been installed.
|
|
106
106
|
*
|
|
107
|
-
* @param {string} projectRoot
|
|
108
107
|
* @param {string} platform - 'claude' | 'codebuddy' | 'codex' | 'all' ('both' supported only for legacy manifests)
|
|
108
|
+
* @param {string} [aiCli] - optional AI CLI command that may require an additional payload platform
|
|
109
109
|
*/
|
|
110
|
-
export async function cleanExternalSkillArtifacts(projectRoot, platform) {
|
|
110
|
+
export async function cleanExternalSkillArtifacts(projectRoot, platform, aiCli = '') {
|
|
111
111
|
const keepDirs = new Set(
|
|
112
|
-
getTargetDirs(platform, projectRoot).map(d => path.resolve(d))
|
|
112
|
+
getTargetDirs(platform, projectRoot, aiCli).map(d => path.resolve(d))
|
|
113
113
|
);
|
|
114
114
|
|
|
115
|
-
const selectedPlatforms =
|
|
115
|
+
const selectedPlatforms = resolvePayloadPlatforms(platform, aiCli);
|
|
116
116
|
const dirsToCheck = [
|
|
117
117
|
selectedPlatforms.includes('codex') ? null : '.agents',
|
|
118
118
|
'.agent',
|
|
@@ -152,7 +152,7 @@ export async function cleanExternalSkillArtifacts(projectRoot, platform) {
|
|
|
152
152
|
const abs = path.join(projectRoot, dir);
|
|
153
153
|
if (!await fs.pathExists(abs)) continue;
|
|
154
154
|
// Only remove if this platform dir is entirely unneeded
|
|
155
|
-
const isKept = getTargetDirs(platform, projectRoot)
|
|
155
|
+
const isKept = getTargetDirs(platform, projectRoot, aiCli)
|
|
156
156
|
.some(d => path.resolve(d).startsWith(path.resolve(abs) + path.sep));
|
|
157
157
|
if (isKept) continue;
|
|
158
158
|
try {
|
|
@@ -166,8 +166,8 @@ export async function cleanExternalSkillArtifacts(projectRoot, platform) {
|
|
|
166
166
|
await fs.remove(path.join(projectRoot, 'skills-lock.json')).catch(() => {});
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
function getTargetDirs(platform, projectRoot) {
|
|
170
|
-
return
|
|
169
|
+
function getTargetDirs(platform, projectRoot, aiCli = '') {
|
|
170
|
+
return resolvePayloadPlatforms(platform, aiCli).map(p => {
|
|
171
171
|
if (p === 'codebuddy') return path.join(projectRoot, '.codebuddy', 'skills');
|
|
172
172
|
if (p === 'codex') return path.join(projectRoot, '.agents', 'skills');
|
|
173
173
|
return path.join(projectRoot, '.claude', 'skills');
|
package/src/manifest.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import fs from 'fs-extra';
|
|
11
11
|
import path from 'path';
|
|
12
|
-
import {
|
|
12
|
+
import { projectMemoryFile, resolvePayloadPlatforms } from './platforms.js';
|
|
13
13
|
import { normalizeRuntime } from './runtimes.js';
|
|
14
14
|
|
|
15
15
|
const MANIFEST_FILE = 'manifest.json';
|
|
@@ -58,7 +58,7 @@ export async function writeManifest(projectRoot, data) {
|
|
|
58
58
|
* @param {boolean} params.team - whether team config was installed
|
|
59
59
|
* @param {string} [params.aiCli] - AI CLI command
|
|
60
60
|
* @param {string} [params.rulesPreset] - rules preset name
|
|
61
|
-
* @param {string[]} [params.
|
|
61
|
+
* @param {string[]} [params.payloadPlatforms] - concrete platform payloads installed for platform + aiCli
|
|
62
62
|
* @returns {Object} manifest
|
|
63
63
|
*/
|
|
64
64
|
export function buildManifest({
|
|
@@ -74,8 +74,12 @@ export function buildManifest({
|
|
|
74
74
|
aiCli,
|
|
75
75
|
rulesPreset,
|
|
76
76
|
extras,
|
|
77
|
+
payloadPlatforms,
|
|
77
78
|
}) {
|
|
78
79
|
const now = new Date().toISOString();
|
|
80
|
+
const installedPayloadPlatforms = payloadPlatforms?.length
|
|
81
|
+
? [...new Set(payloadPlatforms)]
|
|
82
|
+
: resolvePayloadPlatforms(platform, aiCli);
|
|
79
83
|
return {
|
|
80
84
|
version,
|
|
81
85
|
installedAt: now,
|
|
@@ -95,7 +99,8 @@ export function buildManifest({
|
|
|
95
99
|
rules: [...rules],
|
|
96
100
|
pipeline: pipeline ? [...pipeline] : [],
|
|
97
101
|
extras: extras ? [...extras] : [],
|
|
98
|
-
|
|
102
|
+
payloadPlatforms: installedPayloadPlatforms,
|
|
103
|
+
other: [...new Set(installedPayloadPlatforms.map(projectMemoryFile).filter(Boolean))],
|
|
99
104
|
},
|
|
100
105
|
};
|
|
101
106
|
}
|
package/src/platforms.js
CHANGED
|
@@ -10,6 +10,31 @@ export function expandPlatforms(platform) {
|
|
|
10
10
|
return PLATFORM_GROUPS[platform] || LEGACY_PLATFORM_GROUPS[platform] || [platform];
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export function platformFromAiCli(aiCli) {
|
|
14
|
+
if (!aiCli || typeof aiCli !== 'string') return null;
|
|
15
|
+
const token = aiCli.trim().split(/\s+/)[0]?.replace(/^['"]|['"]$/g, '');
|
|
16
|
+
const name = token.split(/[\\/]/).pop()?.toLowerCase() || '';
|
|
17
|
+
if (name.includes('claude')) return 'claude';
|
|
18
|
+
if (name.includes('codex')) return 'codex';
|
|
19
|
+
if (name.includes('cbc') || name.includes('codebuddy')) return 'codebuddy';
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function uniquePlatforms(platforms) {
|
|
24
|
+
return [...new Set(platforms.filter(p => PLATFORM_IDS.includes(p)))];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function resolvePayloadPlatforms(platform, aiCli = '') {
|
|
28
|
+
const selected = uniquePlatforms(expandPlatforms(platform));
|
|
29
|
+
const cliPlatform = platformFromAiCli(aiCli);
|
|
30
|
+
return uniquePlatforms([...selected, cliPlatform].filter(Boolean));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function payloadPlatformLabel(platform, aiCli = '') {
|
|
34
|
+
const payload = resolvePayloadPlatforms(platform, aiCli);
|
|
35
|
+
return payload.map(platformLabel).join(' + ');
|
|
36
|
+
}
|
|
37
|
+
|
|
13
38
|
export function isKnownPlatform(platform) {
|
|
14
39
|
return PLATFORM_IDS.includes(platform) || Object.hasOwn(PLATFORM_GROUPS, platform);
|
|
15
40
|
}
|
package/src/scaffold.js
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
} from './metadata.js';
|
|
28
28
|
import { generateGitignore } from './gitignore-template.js';
|
|
29
29
|
import { buildManifest, writeManifest } from './manifest.js';
|
|
30
|
-
import { defaultCliForPlatform, expandPlatforms, isKnownPlatform, platformLabel, projectMemoryFile } from './platforms.js';
|
|
30
|
+
import { defaultCliForPlatform, expandPlatforms, isKnownPlatform, platformLabel, projectMemoryFile, resolvePayloadPlatforms } from './platforms.js';
|
|
31
31
|
import { normalizeRuntime, runtimeLabel } from './runtimes.js';
|
|
32
32
|
|
|
33
33
|
const __scaffoldDirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -1436,6 +1436,7 @@ export async function scaffold(config) {
|
|
|
1436
1436
|
throw new Error(`Unknown platform "${platform}". Expected codebuddy, claude, codex, or all.`);
|
|
1437
1437
|
}
|
|
1438
1438
|
const platforms = expandPlatforms(platform);
|
|
1439
|
+
const payloadPlatforms = resolvePayloadPlatforms(platform, aiCli);
|
|
1439
1440
|
|
|
1440
1441
|
if (dryRun) {
|
|
1441
1442
|
console.log(chalk.yellow('\n [DRY RUN] 预览将要安装的内容:\n'));
|
|
@@ -1446,8 +1447,8 @@ export async function scaffold(config) {
|
|
|
1446
1447
|
|
|
1447
1448
|
console.log('');
|
|
1448
1449
|
|
|
1449
|
-
for (const p of
|
|
1450
|
-
console.log(chalk.bold(` 正在安装 ${platformLabel(p)}
|
|
1450
|
+
for (const p of payloadPlatforms) {
|
|
1451
|
+
console.log(chalk.bold(` 正在安装 ${platformLabel(p)} 环境...${platforms.includes(p) ? '' : chalk.gray(' (AI CLI payload)')}\n`));
|
|
1451
1452
|
|
|
1452
1453
|
// 1. Skills
|
|
1453
1454
|
console.log(chalk.blue(' 技能文件:'));
|
|
@@ -1515,7 +1516,7 @@ export async function scaffold(config) {
|
|
|
1515
1516
|
const skillDef = knownSkills.find(s => s.name === name);
|
|
1516
1517
|
if (skillDef) {
|
|
1517
1518
|
try {
|
|
1518
|
-
await installExternalSkill(skillDef, platform, projectRoot, dryRun);
|
|
1519
|
+
await installExternalSkill(skillDef, platform, projectRoot, dryRun, aiCli);
|
|
1519
1520
|
console.log(chalk.green(` ✓ ${name} (external)`));
|
|
1520
1521
|
} catch (e) {
|
|
1521
1522
|
console.log(chalk.yellow(` ⚠ ${name} 安装失败,跳过: ${e.message}`));
|
|
@@ -1523,7 +1524,7 @@ export async function scaffold(config) {
|
|
|
1523
1524
|
}
|
|
1524
1525
|
}
|
|
1525
1526
|
if (!dryRun) {
|
|
1526
|
-
await cleanExternalSkillArtifacts(projectRoot, platform);
|
|
1527
|
+
await cleanExternalSkillArtifacts(projectRoot, platform, aiCli);
|
|
1527
1528
|
}
|
|
1528
1529
|
}
|
|
1529
1530
|
|
|
@@ -1545,7 +1546,7 @@ export async function scaffold(config) {
|
|
|
1545
1546
|
// 11. Clean up stray .codebuddy/ directory left by third-party tools (e.g. npx skills)
|
|
1546
1547
|
// when installing for Claude Code only. CodeBuddy files should never appear in a
|
|
1547
1548
|
// claude-only install.
|
|
1548
|
-
if (!dryRun && !
|
|
1549
|
+
if (!dryRun && !payloadPlatforms.includes('codebuddy')) {
|
|
1549
1550
|
const strayCbDir = path.join(projectRoot, '.codebuddy');
|
|
1550
1551
|
if (await fs.pathExists(strayCbDir)) {
|
|
1551
1552
|
const entries = await fs.readdir(strayCbDir);
|
|
@@ -1584,6 +1585,7 @@ export async function scaffold(config) {
|
|
|
1584
1585
|
aiCli,
|
|
1585
1586
|
rulesPreset: rulesPresetName,
|
|
1586
1587
|
extras: activeExtras,
|
|
1588
|
+
payloadPlatforms,
|
|
1587
1589
|
});
|
|
1588
1590
|
await writeManifest(projectRoot, manifest);
|
|
1589
1591
|
console.log(chalk.green(' ✓ .prizmkit/manifest.json'));
|
|
@@ -1632,6 +1634,9 @@ export async function scaffold(config) {
|
|
|
1632
1634
|
console.log(chalk.gray(` 安装统计:`));
|
|
1633
1635
|
console.log(chalk.gray(` 技能: ${suiteLabel}`));
|
|
1634
1636
|
console.log(chalk.gray(` 平台: ${platforms.map(platformLabel).join(' + ')}`));
|
|
1637
|
+
if (payloadPlatforms.length !== platforms.length || payloadPlatforms.some(p => !platforms.includes(p))) {
|
|
1638
|
+
console.log(chalk.gray(` AI CLI 载荷平台: ${payloadPlatforms.map(platformLabel).join(' + ')}`));
|
|
1639
|
+
}
|
|
1635
1640
|
console.log(chalk.gray(` 运行时: ${runtimeLabel(runtime)}`));
|
|
1636
1641
|
console.log(chalk.gray(` 团队: ${team ? '已启用' : '未启用'}`));
|
|
1637
1642
|
console.log(chalk.gray(` 流水线: ${pipeline ? '已安装' : '未安装'}`));
|
package/src/upgrade.js
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
installGitHook,
|
|
28
28
|
installGitignore,
|
|
29
29
|
installProjectMemory,
|
|
30
|
+
installTeamConfig,
|
|
30
31
|
resolvePipelineFileList,
|
|
31
32
|
findStaleManagedPipelineFiles,
|
|
32
33
|
removeStaleManagedPipelineFiles,
|
|
@@ -34,7 +35,7 @@ import {
|
|
|
34
35
|
resolveSkillList,
|
|
35
36
|
EXTRAS_REGISTRY,
|
|
36
37
|
} from './scaffold.js';
|
|
37
|
-
import {
|
|
38
|
+
import { platformLabel, resolvePayloadPlatforms } from './platforms.js';
|
|
38
39
|
import { normalizeRuntime, runtimeLabel } from './runtimes.js';
|
|
39
40
|
|
|
40
41
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -276,6 +277,8 @@ export async function runUpgrade(directory, options = {}) {
|
|
|
276
277
|
...(!oldManifest?.files?.extras ? Object.keys(EXTRAS_REGISTRY) : []),
|
|
277
278
|
])].filter(name => EXTRAS_REGISTRY[name]); // only keep extras that still exist in registry
|
|
278
279
|
|
|
280
|
+
const payloadPlatforms = resolvePayloadPlatforms(platform, aiCli);
|
|
281
|
+
|
|
279
282
|
// 4. Build new manifest and compute diff
|
|
280
283
|
const newManifest = buildManifest({
|
|
281
284
|
version: pkg.version,
|
|
@@ -290,6 +293,7 @@ export async function runUpgrade(directory, options = {}) {
|
|
|
290
293
|
aiCli,
|
|
291
294
|
rulesPreset,
|
|
292
295
|
extras: extrasToInstall,
|
|
296
|
+
payloadPlatforms,
|
|
293
297
|
});
|
|
294
298
|
|
|
295
299
|
// Preserve original installedAt
|
|
@@ -349,7 +353,7 @@ export async function runUpgrade(directory, options = {}) {
|
|
|
349
353
|
}
|
|
350
354
|
|
|
351
355
|
// 7. Execute
|
|
352
|
-
const platforms =
|
|
356
|
+
const platforms = payloadPlatforms;
|
|
353
357
|
|
|
354
358
|
// 7a. Remove orphaned files
|
|
355
359
|
if (diff.skills.removed.length || diff.agents.removed.length || diff.rules.removed.length || diff.pipeline.removed.length || staleManagedPipelineFiles.length) {
|
|
@@ -396,6 +400,11 @@ export async function runUpgrade(directory, options = {}) {
|
|
|
396
400
|
console.log(chalk.blue('\n Settings & Rules:'));
|
|
397
401
|
await installSettings(p, projectRoot, { pipeline, rules: rulesPreset }, dryRun, runtime);
|
|
398
402
|
|
|
403
|
+
if (team) {
|
|
404
|
+
console.log(chalk.blue('\n Team Config:'));
|
|
405
|
+
await installTeamConfig(p, projectRoot, dryRun);
|
|
406
|
+
}
|
|
407
|
+
|
|
399
408
|
console.log('');
|
|
400
409
|
}
|
|
401
410
|
|