prizmkit 1.1.153 → 1.1.155
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/adapters/codex/skill-adapter.js +1 -1
- package/bundled/adapters/pi/skill-adapter.js +2 -1
- package/bundled/dev-pipeline/.env.example +2 -6
- package/bundled/dev-pipeline/README.md +120 -113
- package/bundled/dev-pipeline/assets/skill-subagent-integration.md +1 -1
- package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +355 -15
- package/bundled/dev-pipeline/prizmkit_runtime/cli.py +189 -110
- package/bundled/dev-pipeline/prizmkit_runtime/commands.py +146 -111
- package/bundled/dev-pipeline/prizmkit_runtime/daemon.py +4 -20
- package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +48 -14
- package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +0 -1
- package/bundled/dev-pipeline/prizmkit_runtime/paths.py +0 -3
- package/bundled/dev-pipeline/prizmkit_runtime/reset.py +122 -15
- package/bundled/dev-pipeline/prizmkit_runtime/reset_preserve.py +1 -1
- package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +50 -2
- package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +1 -10
- package/bundled/dev-pipeline/prizmkit_runtime/runner_prompts.py +3 -3
- package/bundled/dev-pipeline/prizmkit_runtime/runners.py +139 -74
- package/bundled/dev-pipeline/prizmkit_runtime/runtime_commit.py +482 -0
- package/bundled/dev-pipeline/prizmkit_runtime/status.py +106 -1
- package/bundled/dev-pipeline/prizmkit_runtime/test_result.py +64 -11
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +145 -367
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +91 -62
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +105 -69
- package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +2 -2
- package/bundled/dev-pipeline/scripts/prompt_framework.py +36 -41
- package/bundled/dev-pipeline/scripts/update-bug-status.py +197 -71
- package/bundled/dev-pipeline/scripts/update-checkpoint.py +77 -16
- package/bundled/dev-pipeline/scripts/update-feature-status.py +46 -121
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +46 -123
- package/bundled/dev-pipeline/scripts/utils.py +119 -0
- package/bundled/dev-pipeline/templates/bootstrap-prompt.md +22 -27
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +71 -104
- package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +3 -2
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +59 -111
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +37 -72
- package/bundled/dev-pipeline/templates/sections/artifact-manifest.md +39 -0
- package/bundled/dev-pipeline/templates/sections/bugfix-critical-paths.md +3 -1
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-commit-report.md +8 -25
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +3 -3
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-implement.md +2 -2
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +3 -3
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-test.md +2 -2
- package/bundled/dev-pipeline/templates/sections/bugfix-reminders.md +4 -4
- package/bundled/dev-pipeline/templates/sections/bugfix-session-context.md +0 -1
- package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +7 -4
- package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +14 -27
- package/bundled/dev-pipeline/templates/sections/feature-completion-summary.md +20 -0
- package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +8 -71
- package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +7 -7
- package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +3 -3
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +4 -4
- package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +5 -17
- package/bundled/dev-pipeline/templates/sections/refactor-critical-paths.md +3 -1
- package/bundled/dev-pipeline/templates/sections/refactor-phase-commit-report.md +10 -23
- package/bundled/dev-pipeline/templates/sections/refactor-phase-implement.md +2 -2
- package/bundled/dev-pipeline/templates/sections/refactor-phase-plan.md +2 -2
- package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +3 -3
- package/bundled/dev-pipeline/templates/sections/refactor-phase-test.md +2 -2
- package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +7 -7
- package/bundled/dev-pipeline/templates/sections/refactor-session-context.md +0 -1
- package/bundled/dev-pipeline/templates/sections/retrospective.md +12 -0
- package/bundled/dev-pipeline/templates/sections/runtime-commit-handoff.md +35 -0
- package/bundled/dev-pipeline/templates/session-status-schema.json +45 -62
- package/bundled/dev-pipeline/tests/test_auto_skip.py +111 -32
- package/bundled/dev-pipeline/tests/test_checkpoint_state.py +617 -30
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +262 -194
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +62 -23
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +76 -28
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +281 -206
- package/bundled/dev-pipeline/tests/test_recovery_workflow.py +322 -0
- package/bundled/dev-pipeline/tests/test_reset_modes.py +935 -0
- package/bundled/dev-pipeline/tests/test_reset_preserve.py +5 -7
- package/bundled/dev-pipeline/tests/test_runtime_commit.py +608 -0
- package/bundled/dev-pipeline/tests/test_unified_cli.py +617 -188
- package/bundled/skills/_metadata.json +9 -9
- package/bundled/skills/app-planner/SKILL.md +3 -3
- package/bundled/skills/app-planner/references/generated-plan-review.md +2 -1
- package/bundled/skills/app-planner/references/rules/backend/derivation-rules.md +56 -64
- package/bundled/skills/app-planner/references/rules/backend/fixed-rules.md +85 -240
- package/bundled/skills/app-planner/references/rules/backend/question-bank.md +25 -25
- package/bundled/skills/app-planner/references/rules/backend/question-manifest.json +16 -7
- package/bundled/skills/app-planner/references/rules/backend/template.md +74 -92
- package/bundled/skills/app-planner/references/rules/database/derivation-rules.md +93 -93
- package/bundled/skills/app-planner/references/rules/database/fixed-rules.md +66 -173
- package/bundled/skills/app-planner/references/rules/database/question-bank.md +31 -26
- package/bundled/skills/app-planner/references/rules/database/question-manifest.json +13 -4
- package/bundled/skills/app-planner/references/rules/database/template.md +76 -98
- package/bundled/skills/app-planner/references/rules/frontend/derivation-rules.md +68 -95
- package/bundled/skills/app-planner/references/rules/frontend/fixed-rules.md +93 -166
- package/bundled/skills/app-planner/references/rules/frontend/question-bank.md +46 -36
- package/bundled/skills/app-planner/references/rules/frontend/question-manifest.json +11 -2
- package/bundled/skills/app-planner/references/rules/frontend/template.md +79 -257
- package/bundled/skills/app-planner/references/rules/mobile/derivation-rules.md +91 -99
- package/bundled/skills/app-planner/references/rules/mobile/fixed-rules.md +73 -246
- package/bundled/skills/app-planner/references/rules/mobile/question-bank.md +17 -17
- package/bundled/skills/app-planner/references/rules/mobile/question-manifest.json +17 -2
- package/bundled/skills/app-planner/references/rules/mobile/template.md +73 -85
- package/bundled/skills/app-planner/references/rules-configuration.md +133 -65
- package/bundled/skills/app-planner/scripts/validate-rules-configuration.py +291 -0
- package/bundled/skills/bug-planner/references/schema-validation.md +1 -1
- package/bundled/skills/bug-planner/scripts/validate-bug-list.py +1 -1
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +21 -13
- package/bundled/skills/feature-pipeline-launcher/SKILL.md +22 -14
- package/bundled/skills/prizmkit/SKILL.md +17 -18
- package/bundled/skills/prizmkit/references/workflow-state-protocol.md +22 -18
- package/bundled/skills/prizmkit-code-review/SKILL.md +22 -58
- package/bundled/skills/prizmkit-code-review/references/independent-code-review.md +6 -6
- package/bundled/skills/prizmkit-code-review/references/review-report-template.md +2 -2
- package/bundled/skills/prizmkit-committer/SKILL.md +82 -132
- package/bundled/skills/prizmkit-deploy/SKILL.md +27 -11
- package/bundled/skills/prizmkit-deploy/assets/deploy-document-template.md +89 -0
- package/bundled/skills/prizmkit-deploy/references/ci-cd-workflows.md +44 -14
- package/bundled/skills/prizmkit-deploy/references/cloud-platform-deploy.md +19 -3
- package/bundled/skills/prizmkit-deploy/references/database-setup.md +22 -12
- package/bundled/skills/prizmkit-deploy/references/deploy-config-schema.md +3 -1
- package/bundled/skills/prizmkit-deploy/references/deploy-history-schema.md +22 -6
- package/bundled/skills/prizmkit-deploy/references/deploy-metadata-schema.json +49 -0
- package/bundled/skills/prizmkit-deploy/references/live-validation-notes.md +1 -1
- package/bundled/skills/prizmkit-deploy/references/pending-input-schema.json +164 -0
- package/bundled/skills/prizmkit-deploy/references/ssh-adapter-flow.md +9 -8
- package/bundled/skills/prizmkit-deploy/references/ssh-execution-flow.md +6 -6
- package/bundled/skills/prizmkit-implement/SKILL.md +30 -66
- package/bundled/skills/prizmkit-implement/references/implementation-subagent-procedure.md +4 -4
- package/bundled/skills/prizmkit-init/SKILL.md +4 -0
- package/bundled/skills/prizmkit-plan/SKILL.md +21 -64
- package/bundled/skills/prizmkit-plan/references/artifact-identity.md +38 -0
- package/bundled/skills/prizmkit-plan/references/independent-plan-review.md +4 -4
- package/bundled/skills/prizmkit-plan/references/review-plan-spec-loop.md +5 -5
- package/bundled/skills/prizmkit-prizm-docs/SKILL.md +11 -1
- package/bundled/skills/prizmkit-retrospective/SKILL.md +63 -77
- package/bundled/skills/prizmkit-retrospective/references/knowledge-injection-steps.md +3 -3
- package/bundled/skills/prizmkit-retrospective/references/retrospective-result-schema.json +138 -0
- package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +17 -23
- package/bundled/skills/prizmkit-test/SKILL.md +64 -51
- package/bundled/skills/prizmkit-test/references/boundary-contract-and-test-double-guidance.md +206 -0
- package/bundled/skills/prizmkit-test/references/independent-test-review.md +17 -9
- package/bundled/skills/prizmkit-test/references/test-coverage-model.md +45 -21
- package/bundled/skills/prizmkit-test/references/test-report-template.md +20 -13
- package/bundled/skills/prizmkit-workflow/SKILL.md +35 -37
- package/bundled/skills/prizmkit-workflow/references/artifact-identity.md +34 -0
- package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +22 -18
- package/bundled/skills/recovery-workflow/SKILL.md +9 -6
- package/bundled/skills/recovery-workflow/evals/evals.json +2 -2
- package/bundled/skills/recovery-workflow/references/detection.md +7 -6
- package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +16 -6
- package/bundled/skills/refactor-pipeline-launcher/SKILL.md +21 -13
- package/bundled/templates/project-memory-template.md +19 -11
- package/package.json +1 -1
- package/src/scaffold.js +33 -22
- package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +0 -228
- package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +0 -767
- package/bundled/dev-pipeline/templates/sections/bugfix-artifacts.md +0 -16
- package/bundled/dev-pipeline/templates/sections/bugfix-session-status.md +0 -31
- package/bundled/dev-pipeline/templates/sections/critical-paths-agent.md +0 -7
- package/bundled/dev-pipeline/templates/sections/critical-paths-lite.md +0 -7
- package/bundled/dev-pipeline/templates/sections/directory-convention-agent.md +0 -9
- package/bundled/dev-pipeline/templates/sections/directory-convention-lite.md +0 -6
- package/bundled/dev-pipeline/templates/sections/headless-commit-authorization.md +0 -15
- package/bundled/dev-pipeline/templates/sections/phase-commit.md +0 -61
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +0 -24
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-lite-suffix.md +0 -12
- package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +0 -44
- package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +0 -25
- package/bundled/dev-pipeline/templates/sections/phase-plan-agent.md +0 -20
- package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +0 -20
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +0 -20
- package/bundled/dev-pipeline/templates/sections/refactor-artifacts.md +0 -17
- package/bundled/dev-pipeline/templates/sections/refactor-session-status.md +0 -28
- package/bundled/dev-pipeline/templates/sections/test-failure-recovery-agent.md +0 -52
- package/bundled/dev-pipeline/templates/sections/test-failure-recovery-lite.md +0 -52
- package/bundled/skills/prizmkit-code-review/references/workflow-state-protocol.md +0 -174
- package/bundled/skills/prizmkit-committer/references/workflow-state-protocol.md +0 -174
- package/bundled/skills/prizmkit-implement/references/workflow-state-protocol.md +0 -174
- package/bundled/skills/prizmkit-plan/references/workflow-state-protocol.md +0 -174
- package/bundled/skills/prizmkit-retrospective/references/workflow-state-protocol.md +0 -174
- package/bundled/skills/prizmkit-test/references/external-contract-mock-guidance.md +0 -119
|
@@ -76,6 +76,102 @@ def _write_family_plan(paths, kind, items):
|
|
|
76
76
|
return plan_path
|
|
77
77
|
|
|
78
78
|
|
|
79
|
+
@pytest.mark.parametrize(
|
|
80
|
+
("kind", "item_id", "filename", "list_key", "schema", "item"),
|
|
81
|
+
[
|
|
82
|
+
(
|
|
83
|
+
"feature", "F-001", "feature-list.json", "features",
|
|
84
|
+
"dev-pipeline-feature-list-v1",
|
|
85
|
+
{
|
|
86
|
+
"id": "F-001", "title": "Preview Feature", "description": "Preview only",
|
|
87
|
+
"priority": "high", "dependencies": [],
|
|
88
|
+
"acceptance_criteria": ["Prompt validates"], "status": "pending",
|
|
89
|
+
},
|
|
90
|
+
),
|
|
91
|
+
(
|
|
92
|
+
"bugfix", "B-001", "bug-fix-list.json", "bugs",
|
|
93
|
+
"dev-pipeline-bug-fix-list-v1",
|
|
94
|
+
{
|
|
95
|
+
"id": "B-001", "title": "Preview Bug", "description": "Preview only",
|
|
96
|
+
"severity": "high", "priority": "high", "dependencies": [],
|
|
97
|
+
"error_source": {"type": "user_report", "reproduction_steps": ["preview"]},
|
|
98
|
+
"verification_type": "automated",
|
|
99
|
+
"acceptance_criteria": ["Prompt validates"], "status": "pending",
|
|
100
|
+
},
|
|
101
|
+
),
|
|
102
|
+
(
|
|
103
|
+
"refactor", "R-001", "refactor-list.json", "refactors",
|
|
104
|
+
"dev-pipeline-refactor-list-v1",
|
|
105
|
+
{
|
|
106
|
+
"id": "R-001", "title": "Preview Refactor", "description": "Preview only",
|
|
107
|
+
"scope": {"files": ["src/example.js"], "modules": ["example"]},
|
|
108
|
+
"type": "simplify", "priority": "high", "complexity": "low",
|
|
109
|
+
"behavior_preservation": {"strategy": "test-gate", "existing_tests": True},
|
|
110
|
+
"acceptance_criteria": ["Prompt validates"],
|
|
111
|
+
"dependencies": [], "status": "pending",
|
|
112
|
+
},
|
|
113
|
+
),
|
|
114
|
+
],
|
|
115
|
+
)
|
|
116
|
+
def test_actual_dry_run_is_read_only_and_successful(
|
|
117
|
+
tmp_path, kind, item_id, filename, list_key, schema, item,
|
|
118
|
+
):
|
|
119
|
+
from prizmkit_runtime.paths import resolve_runtime_paths
|
|
120
|
+
from prizmkit_runtime.runners import run_pipeline_command
|
|
121
|
+
|
|
122
|
+
project = tmp_path / kind
|
|
123
|
+
plans = project / ".prizmkit" / "plans"
|
|
124
|
+
plans.mkdir(parents=True)
|
|
125
|
+
plan_path = plans / filename
|
|
126
|
+
plan_path.write_text(
|
|
127
|
+
json.dumps({"$schema": schema, "project_name": "Dry Run", list_key: [item]}),
|
|
128
|
+
encoding="utf-8",
|
|
129
|
+
)
|
|
130
|
+
fake_cli = project / "fake-ai.py"
|
|
131
|
+
fake_cli.write_text(
|
|
132
|
+
"#!/usr/bin/env python3\nfrom pathlib import Path\nPath('AI_WAS_LAUNCHED').write_text('bad')\n",
|
|
133
|
+
encoding="utf-8",
|
|
134
|
+
)
|
|
135
|
+
fake_cli.chmod(0o755)
|
|
136
|
+
(project / ".prizmkit" / "config.json").write_text(
|
|
137
|
+
json.dumps({
|
|
138
|
+
"platform": "pi",
|
|
139
|
+
"ai_cli": str(fake_cli),
|
|
140
|
+
"ai_cli_launch": {"profile": "custom", "prompt_arg": "--prompt"},
|
|
141
|
+
}),
|
|
142
|
+
encoding="utf-8",
|
|
143
|
+
)
|
|
144
|
+
subprocess.run(["git", "init", "-b", "main"], cwd=project, check=True, stdout=subprocess.DEVNULL)
|
|
145
|
+
subprocess.run(["git", "config", "user.name", "Dry Run Test"], cwd=project, check=True)
|
|
146
|
+
subprocess.run(["git", "config", "user.email", "dry-run@example.invalid"], cwd=project, check=True)
|
|
147
|
+
subprocess.run(["git", "add", "."], cwd=project, check=True)
|
|
148
|
+
subprocess.run(["git", "commit", "-m", "initial"], cwd=project, check=True, stdout=subprocess.DEVNULL)
|
|
149
|
+
paths = resolve_runtime_paths(pipeline_root=PIPELINE_ROOT, project_root=project)
|
|
150
|
+
branch_before = subprocess.check_output(["git", "branch", "--show-current"], cwd=project, text=True).strip()
|
|
151
|
+
head_before = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=project, text=True).strip()
|
|
152
|
+
status_before = subprocess.check_output(["git", "status", "--porcelain=v1", "-uall"], cwd=project, text=True)
|
|
153
|
+
|
|
154
|
+
run_args = (
|
|
155
|
+
(str(plan_path), "--dry-run")
|
|
156
|
+
if kind == "feature"
|
|
157
|
+
else (item_id, str(plan_path), "--dry-run")
|
|
158
|
+
)
|
|
159
|
+
result = run_pipeline_command(kind, "run", run_args, paths)
|
|
160
|
+
|
|
161
|
+
assert result.exit_code == 0
|
|
162
|
+
assert "processed=1 completed=False stopped_reason=dry_run" in result.render()
|
|
163
|
+
assert "last_status: dry_run" in result.render()
|
|
164
|
+
assert json.loads(plan_path.read_text(encoding="utf-8"))[list_key][0]["status"] == "pending"
|
|
165
|
+
assert subprocess.check_output(["git", "branch", "--show-current"], cwd=project, text=True).strip() == branch_before
|
|
166
|
+
assert subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=project, text=True).strip() == head_before
|
|
167
|
+
assert subprocess.check_output(["git", "status", "--porcelain=v1", "-uall"], cwd=project, text=True) == status_before
|
|
168
|
+
assert not (project / "AI_WAS_LAUNCHED").exists()
|
|
169
|
+
assert not (project / ".prizmkit" / "state").exists()
|
|
170
|
+
assert not (project / ".prizmkit" / "specs").exists()
|
|
171
|
+
assert not (project / ".prizmkit" / "bugfix").exists()
|
|
172
|
+
assert not (project / ".prizmkit" / "refactor").exists()
|
|
173
|
+
|
|
174
|
+
|
|
79
175
|
def _get_next_result(kind, paths):
|
|
80
176
|
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
81
177
|
from prizmkit_runtime.runner_status import get_next
|
|
@@ -97,22 +193,22 @@ def _write_semantic_completion_checkpoint(project_root, artifact_dir, workflow_t
|
|
|
97
193
|
"feature_state",
|
|
98
194
|
[
|
|
99
195
|
"prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
|
|
100
|
-
"
|
|
196
|
+
"completion-summary", "prizmkit-retrospective", "prizmkit-committer",
|
|
101
197
|
],
|
|
102
198
|
),
|
|
103
199
|
"bugfix-pipeline": (
|
|
104
200
|
"bugfix_state",
|
|
105
201
|
[
|
|
106
202
|
"prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
|
|
107
|
-
"
|
|
203
|
+
"bug-report", "prizmkit-retrospective", "prizmkit-committer",
|
|
108
204
|
],
|
|
109
205
|
),
|
|
110
206
|
"refactor-pipeline": (
|
|
111
207
|
"refactor_state",
|
|
112
208
|
[
|
|
113
209
|
"prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
|
|
114
|
-
"
|
|
115
|
-
"
|
|
210
|
+
"refactor-report", "completion-summary", "prizmkit-retrospective",
|
|
211
|
+
"prizmkit-committer",
|
|
116
212
|
],
|
|
117
213
|
),
|
|
118
214
|
}
|
|
@@ -128,6 +224,8 @@ def _write_semantic_completion_checkpoint(project_root, artifact_dir, workflow_t
|
|
|
128
224
|
"required_artifacts": [],
|
|
129
225
|
"depends_on": previous,
|
|
130
226
|
}
|
|
227
|
+
if skill == "prizmkit-implement":
|
|
228
|
+
step["stage_result"] = "IMPLEMENTED"
|
|
131
229
|
if skill == "prizmkit-code-review":
|
|
132
230
|
step["stage_result"] = "REVIEW_PASS"
|
|
133
231
|
if skill == "prizmkit-test":
|
|
@@ -137,6 +235,10 @@ def _write_semantic_completion_checkpoint(project_root, artifact_dir, workflow_t
|
|
|
137
235
|
str(relative_artifact / "test-report.md"),
|
|
138
236
|
str(relative_artifact / "test-result.json"),
|
|
139
237
|
]
|
|
238
|
+
if skill == "prizmkit-retrospective":
|
|
239
|
+
step["stage_result"] = "RETRO_COMPLETE"
|
|
240
|
+
if skill == "prizmkit-committer":
|
|
241
|
+
step["stage_result"] = "COMMITTED"
|
|
140
242
|
steps.append(step)
|
|
141
243
|
previous = step["id"]
|
|
142
244
|
artifact_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -158,12 +260,31 @@ def _write_semantic_completion_checkpoint(project_root, artifact_dir, workflow_t
|
|
|
158
260
|
},
|
|
159
261
|
"repair_rounds": 0,
|
|
160
262
|
"production_changed": False,
|
|
161
|
-
"review_required": False,
|
|
162
|
-
"review_scope": None,
|
|
163
263
|
"unresolved_items": [],
|
|
164
264
|
}),
|
|
165
265
|
encoding="utf-8",
|
|
166
266
|
)
|
|
267
|
+
runtime_commit_hash = "b" * 40
|
|
268
|
+
runtime_base_head = "a" * 40
|
|
269
|
+
head = subprocess.run(
|
|
270
|
+
["git", "rev-parse", "HEAD"],
|
|
271
|
+
cwd=project_root,
|
|
272
|
+
stdout=subprocess.PIPE,
|
|
273
|
+
stderr=subprocess.DEVNULL,
|
|
274
|
+
text=True,
|
|
275
|
+
check=False,
|
|
276
|
+
)
|
|
277
|
+
parent = subprocess.run(
|
|
278
|
+
["git", "rev-parse", "HEAD^"],
|
|
279
|
+
cwd=project_root,
|
|
280
|
+
stdout=subprocess.PIPE,
|
|
281
|
+
stderr=subprocess.DEVNULL,
|
|
282
|
+
text=True,
|
|
283
|
+
check=False,
|
|
284
|
+
)
|
|
285
|
+
if head.returncode == 0 and parent.returncode == 0:
|
|
286
|
+
runtime_commit_hash = head.stdout.strip()
|
|
287
|
+
runtime_base_head = parent.stdout.strip()
|
|
167
288
|
checkpoint = artifact_dir / "workflow-checkpoint.json"
|
|
168
289
|
checkpoint.write_text(
|
|
169
290
|
json.dumps({
|
|
@@ -173,6 +294,8 @@ def _write_semantic_completion_checkpoint(project_root, artifact_dir, workflow_t
|
|
|
173
294
|
state_key: {
|
|
174
295
|
"repair_round": 0,
|
|
175
296
|
"terminal_status": "WORKFLOW_COMPLETED",
|
|
297
|
+
"runtime_base_head": runtime_base_head,
|
|
298
|
+
"runtime_commit_hash": runtime_commit_hash,
|
|
176
299
|
},
|
|
177
300
|
}),
|
|
178
301
|
encoding="utf-8",
|
|
@@ -215,9 +338,8 @@ def test_python_parity_suite_declares_coverage_for_all_legacy_unix_snapshot_scen
|
|
|
215
338
|
"entrypointContracts": {
|
|
216
339
|
"runnerHelpContracts": ["test_cli_help_exposes_python_runtime_command_groups"],
|
|
217
340
|
"runnerMissingListErrors": ["test_python_runner_missing_list_errors_match_snapshot_contract"],
|
|
218
|
-
"
|
|
219
|
-
"
|
|
220
|
-
"daemonStatusContracts": ["test_python_entrypoint_contracts_match_snapshot_contracts"],
|
|
341
|
+
"resetHelpContracts": ["test_reset_and_daemon_entrypoint_contracts_match_snapshot_contracts"],
|
|
342
|
+
"daemonStatusContracts": ["test_reset_and_daemon_entrypoint_contracts_match_snapshot_contracts"],
|
|
221
343
|
},
|
|
222
344
|
"envResolution": {
|
|
223
345
|
"verboseAndStreamJson": ["test_python_env_resolution_scenarios_match_snapshot_contracts"],
|
|
@@ -283,8 +405,7 @@ def test_python_runner_missing_list_errors_match_snapshot_contract(tmp_path):
|
|
|
283
405
|
assert "Plan list not found" in result.render()
|
|
284
406
|
|
|
285
407
|
|
|
286
|
-
def
|
|
287
|
-
from prizmkit_runtime.commands import handle_runtime_command
|
|
408
|
+
def test_reset_and_daemon_entrypoint_contracts_match_snapshot_contracts(tmp_path):
|
|
288
409
|
from prizmkit_runtime.daemon import run_daemon_command
|
|
289
410
|
from prizmkit_runtime.paths import resolve_runtime_paths
|
|
290
411
|
from prizmkit_runtime.reset import run_reset_command
|
|
@@ -292,11 +413,6 @@ def test_python_entrypoint_contracts_match_snapshot_contracts(tmp_path):
|
|
|
292
413
|
snapshot = _load_unix_runtime_snapshot()["entrypointContracts"]
|
|
293
414
|
paths = _make_paths(tmp_path / "entrypoints")
|
|
294
415
|
|
|
295
|
-
recovery = handle_runtime_command("recovery", "describe", paths=paths)
|
|
296
|
-
assert recovery.exit_code == snapshot["recoveryHelpContract"]["exitCode"]
|
|
297
|
-
assert "Python runtime command contract" in recovery.render()
|
|
298
|
-
assert "recovery describe" in recovery.render()
|
|
299
|
-
|
|
300
416
|
for kind in ("feature", "bugfix", "refactor"):
|
|
301
417
|
reset = run_reset_command(kind, ("--help",), paths)
|
|
302
418
|
assert reset.exit_code == snapshot["resetHelpContracts"][kind]["exitCode"]
|
|
@@ -410,7 +526,7 @@ def test_runner_models_parse_legacy_args_and_environment(tmp_path):
|
|
|
410
526
|
invocation = parse_invocation(
|
|
411
527
|
family,
|
|
412
528
|
"run",
|
|
413
|
-
("custom-list.json", "F-2", "--features", "F-003:F-001", "--max-retries", "7", "--max-infra-retries=5"
|
|
529
|
+
("custom-list.json", "F-2", "--features", "F-003:F-001", "--max-retries", "7", "--max-infra-retries=5"),
|
|
414
530
|
)
|
|
415
531
|
env = RunnerEnvironment.from_env(
|
|
416
532
|
{
|
|
@@ -429,7 +545,7 @@ def test_runner_models_parse_legacy_args_and_environment(tmp_path):
|
|
|
429
545
|
assert invocation.item_filter == "F-003:F-001"
|
|
430
546
|
assert invocation.max_retries == 7
|
|
431
547
|
assert invocation.max_infra_retries == 5
|
|
432
|
-
assert invocation
|
|
548
|
+
assert not hasattr(invocation, "mode")
|
|
433
549
|
assert env.stop_on_failure is True
|
|
434
550
|
assert env.auto_push is True
|
|
435
551
|
assert env.dev_branch == "dev/custom"
|
|
@@ -722,7 +838,7 @@ def test_status_bridge_passes_max_retries_from_invocation_or_environment(monkeyp
|
|
|
722
838
|
("kind", "item_key"),
|
|
723
839
|
(("feature", "feature_id"), ("bugfix", "bug_id"), ("refactor", "refactor_id")),
|
|
724
840
|
)
|
|
725
|
-
def
|
|
841
|
+
def test_pipeline_aggregate_reselection_keeps_first_seen_details_and_stops(monkeypatch, tmp_path, kind, item_key):
|
|
726
842
|
from prizmkit_runtime import runners
|
|
727
843
|
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
728
844
|
|
|
@@ -743,11 +859,11 @@ def test_pipeline_aggregate_replaces_same_item_status_in_first_seen_order(monkey
|
|
|
743
859
|
|
|
744
860
|
result = runners._run_pipeline(family, invocation, paths)
|
|
745
861
|
|
|
746
|
-
assert result.processed ==
|
|
747
|
-
assert result.completed is
|
|
748
|
-
assert result.stopped_reason == "
|
|
749
|
-
assert result.last_status == "
|
|
750
|
-
assert result.details == ("F-049:completed", "F-050:
|
|
862
|
+
assert result.processed == 2
|
|
863
|
+
assert result.completed is False
|
|
864
|
+
assert result.stopped_reason == "reselected_item"
|
|
865
|
+
assert result.last_status == "pending"
|
|
866
|
+
assert result.details == ("F-049:completed", "F-050:pending")
|
|
751
867
|
|
|
752
868
|
|
|
753
869
|
@pytest.mark.parametrize(
|
|
@@ -785,7 +901,46 @@ def test_pipeline_aggregate_keeps_distinct_unresolved_item_blocking(monkeypatch,
|
|
|
785
901
|
("kind", "item_key"),
|
|
786
902
|
(("feature", "feature_id"), ("bugfix", "bug_id"), ("refactor", "refactor_id")),
|
|
787
903
|
)
|
|
788
|
-
def
|
|
904
|
+
def test_full_list_stops_after_newly_skipped_item(monkeypatch, tmp_path, kind, item_key):
|
|
905
|
+
from prizmkit_runtime import runners
|
|
906
|
+
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
907
|
+
|
|
908
|
+
paths = _make_paths(tmp_path)
|
|
909
|
+
family = family_for(kind, paths)
|
|
910
|
+
invocation = parse_invocation(family, "run", ())
|
|
911
|
+
selector_calls = 0
|
|
912
|
+
processor_calls = []
|
|
913
|
+
|
|
914
|
+
def fake_get_next(*args, **kwargs):
|
|
915
|
+
nonlocal selector_calls
|
|
916
|
+
selector_calls += 1
|
|
917
|
+
if selector_calls > 1:
|
|
918
|
+
raise AssertionError("selector called after terminal skipped item")
|
|
919
|
+
return SimpleNamespace(ok=True, stdout="", data={item_key: "X-001"}, text="")
|
|
920
|
+
|
|
921
|
+
def fake_process(_family, _invocation, item_id, _paths, *, initial_metadata):
|
|
922
|
+
processor_calls.append(item_id)
|
|
923
|
+
return "skipped"
|
|
924
|
+
|
|
925
|
+
monkeypatch.setattr(runners, "get_next", fake_get_next)
|
|
926
|
+
monkeypatch.setattr(runners, "_process_item", fake_process)
|
|
927
|
+
|
|
928
|
+
result = runners._run_pipeline(family, invocation, paths)
|
|
929
|
+
|
|
930
|
+
assert selector_calls == 1
|
|
931
|
+
assert processor_calls == ["X-001"]
|
|
932
|
+
assert result.completed is False
|
|
933
|
+
assert result.processed == 1
|
|
934
|
+
assert result.stopped_reason == "item_skipped"
|
|
935
|
+
assert result.last_status == "skipped"
|
|
936
|
+
assert result.details == ("X-001:skipped",)
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
@pytest.mark.parametrize(
|
|
940
|
+
("kind", "item_key"),
|
|
941
|
+
(("feature", "feature_id"), ("bugfix", "bug_id"), ("refactor", "refactor_id")),
|
|
942
|
+
)
|
|
943
|
+
def test_full_list_rejects_reselected_processed_item_before_second_launch(monkeypatch, tmp_path, kind, item_key):
|
|
789
944
|
from prizmkit_runtime import runners
|
|
790
945
|
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
791
946
|
|
|
@@ -793,20 +948,29 @@ def test_pipeline_aggregate_repeated_terminal_update_remains_unique(monkeypatch,
|
|
|
793
948
|
family = family_for(kind, paths)
|
|
794
949
|
invocation = parse_invocation(family, "run", ())
|
|
795
950
|
queue = [
|
|
796
|
-
SimpleNamespace(ok=True, stdout="", data={item_key: "
|
|
797
|
-
SimpleNamespace(ok=True, stdout="", data={item_key: "
|
|
951
|
+
SimpleNamespace(ok=True, stdout="", data={item_key: "X-001"}, text=""),
|
|
952
|
+
SimpleNamespace(ok=True, stdout="", data={item_key: "X-001"}, text=""),
|
|
798
953
|
SimpleNamespace(ok=True, stdout="PIPELINE_COMPLETE\n", data=None, text="PIPELINE_COMPLETE"),
|
|
799
954
|
]
|
|
955
|
+
processor_calls = []
|
|
800
956
|
|
|
801
957
|
monkeypatch.setattr(runners, "get_next", lambda *args, **kwargs: queue.pop(0))
|
|
802
|
-
|
|
958
|
+
|
|
959
|
+
def fake_process(_family, _invocation, item_id, _paths, *, initial_metadata):
|
|
960
|
+
processor_calls.append(item_id)
|
|
961
|
+
return "completed"
|
|
962
|
+
|
|
963
|
+
monkeypatch.setattr(runners, "_process_item", fake_process)
|
|
803
964
|
monkeypatch.setattr(runners, "_emit_info", lambda *args, **kwargs: None)
|
|
804
965
|
|
|
805
966
|
result = runners._run_pipeline(family, invocation, paths)
|
|
806
967
|
|
|
807
|
-
assert
|
|
808
|
-
assert result.
|
|
968
|
+
assert processor_calls == ["X-001"]
|
|
969
|
+
assert result.completed is False
|
|
970
|
+
assert result.processed == 1
|
|
971
|
+
assert result.stopped_reason == "reselected_item"
|
|
809
972
|
assert result.last_status == "completed"
|
|
973
|
+
assert result.details == ("X-001:completed",)
|
|
810
974
|
|
|
811
975
|
|
|
812
976
|
@pytest.mark.parametrize("kind", ("feature", "bugfix", "refactor"))
|
|
@@ -841,15 +1005,15 @@ def test_pipeline_aggregate_empty_authoritative_completion(monkeypatch, tmp_path
|
|
|
841
1005
|
("kind", "status", "expected_completed"),
|
|
842
1006
|
(
|
|
843
1007
|
("feature", "completed", True),
|
|
844
|
-
("feature", "skipped",
|
|
1008
|
+
("feature", "skipped", False),
|
|
845
1009
|
("feature", "auto_skipped", True),
|
|
846
1010
|
("feature", "split", True),
|
|
847
1011
|
("bugfix", "completed", True),
|
|
848
|
-
("bugfix", "skipped",
|
|
1012
|
+
("bugfix", "skipped", False),
|
|
849
1013
|
("bugfix", "auto_skipped", True),
|
|
850
1014
|
("bugfix", "split", False),
|
|
851
1015
|
("refactor", "completed", True),
|
|
852
|
-
("refactor", "skipped",
|
|
1016
|
+
("refactor", "skipped", False),
|
|
853
1017
|
("refactor", "auto_skipped", True),
|
|
854
1018
|
("refactor", "split", False),
|
|
855
1019
|
),
|
|
@@ -875,7 +1039,9 @@ def test_pipeline_aggregate_completion_compatible_statuses(monkeypatch, tmp_path
|
|
|
875
1039
|
|
|
876
1040
|
assert result.completed is expected_completed
|
|
877
1041
|
assert result.stopped_reason == (
|
|
878
|
-
"
|
|
1042
|
+
"item_skipped"
|
|
1043
|
+
if status == "skipped"
|
|
1044
|
+
else ("pipeline_complete" if expected_completed else "pipeline_complete_with_non_terminal_history")
|
|
879
1045
|
)
|
|
880
1046
|
assert result.last_status == status
|
|
881
1047
|
assert result.details == (f"X-001:{status}",)
|
|
@@ -906,6 +1072,37 @@ def test_run_pipeline_command_omits_empty_last_status_detail(monkeypatch, tmp_pa
|
|
|
906
1072
|
assert result.details == ()
|
|
907
1073
|
|
|
908
1074
|
|
|
1075
|
+
@pytest.mark.parametrize("kind", ("feature", "bugfix", "refactor"))
|
|
1076
|
+
def test_run_pipeline_command_returns_nonzero_for_terminal_skipped_item(monkeypatch, tmp_path, kind):
|
|
1077
|
+
from prizmkit_runtime import runners
|
|
1078
|
+
from prizmkit_runtime.runner_models import PipelineRunResult
|
|
1079
|
+
|
|
1080
|
+
paths = _make_paths(tmp_path)
|
|
1081
|
+
plan_path = {
|
|
1082
|
+
"feature": paths.feature_plan,
|
|
1083
|
+
"bugfix": paths.bugfix_plan,
|
|
1084
|
+
"refactor": paths.refactor_plan,
|
|
1085
|
+
}[kind]
|
|
1086
|
+
plan_path.write_text("{}", encoding="utf-8")
|
|
1087
|
+
monkeypatch.setattr(
|
|
1088
|
+
runners,
|
|
1089
|
+
"_run_pipeline",
|
|
1090
|
+
lambda *args, **kwargs: PipelineRunResult(
|
|
1091
|
+
False,
|
|
1092
|
+
1,
|
|
1093
|
+
"item_skipped",
|
|
1094
|
+
"skipped",
|
|
1095
|
+
("X-001:skipped",),
|
|
1096
|
+
),
|
|
1097
|
+
)
|
|
1098
|
+
|
|
1099
|
+
result = runners.run_pipeline_command(kind, "run", (), paths)
|
|
1100
|
+
|
|
1101
|
+
assert result.exit_code == 1
|
|
1102
|
+
assert result.message == "processed=1 completed=False stopped_reason=item_skipped"
|
|
1103
|
+
assert result.details == ("X-001:skipped", "last_status: skipped")
|
|
1104
|
+
|
|
1105
|
+
|
|
909
1106
|
def test_pipeline_complete_rejects_retryable_detail(monkeypatch, tmp_path):
|
|
910
1107
|
from prizmkit_runtime import runners
|
|
911
1108
|
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
@@ -1008,7 +1205,6 @@ def test_prompt_generation_result_preserves_per_item_model(monkeypatch, tmp_path
|
|
|
1008
1205
|
"output_path": str(session_paths.prompt_path),
|
|
1009
1206
|
"checkpoint_path": str(paths.project_root / ".prizmkit" / "bugfix" / "B-001" / "workflow-checkpoint.json"),
|
|
1010
1207
|
"model": "per-item-model",
|
|
1011
|
-
"pipeline_mode": "standard",
|
|
1012
1208
|
}
|
|
1013
1209
|
),
|
|
1014
1210
|
stderr="",
|
|
@@ -1056,7 +1252,6 @@ def test_prompt_generation_passes_explicit_execution_root_to_generator(monkeypat
|
|
|
1056
1252
|
"output_path": str(session_paths.prompt_path),
|
|
1057
1253
|
"checkpoint_path": str(execution_root / ".prizmkit" / "specs" / "001-low" / "workflow-checkpoint.json"),
|
|
1058
1254
|
"model": "",
|
|
1059
|
-
"pipeline_mode": "full",
|
|
1060
1255
|
}
|
|
1061
1256
|
),
|
|
1062
1257
|
stderr="",
|
|
@@ -1121,7 +1316,6 @@ def _install_runner_session_fakes(monkeypatch, runners, *, statuses=("success",)
|
|
|
1121
1316
|
return PromptGenerationResult(
|
|
1122
1317
|
output_path=session_paths.prompt_path,
|
|
1123
1318
|
model="per-item-model",
|
|
1124
|
-
pipeline_mode="full",
|
|
1125
1319
|
checkpoint_path=execution_root / ".prizmkit" / "specs" / "001-low" / "workflow-checkpoint.json",
|
|
1126
1320
|
artifact_path=execution_root / ".prizmkit" / "specs" / "001-low",
|
|
1127
1321
|
raw={},
|
|
@@ -1454,7 +1648,6 @@ def test_explicit_worktree_materializes_ignored_support_assets_before_prompt_and
|
|
|
1454
1648
|
return PromptGenerationResult(
|
|
1455
1649
|
output_path=session_paths.prompt_path,
|
|
1456
1650
|
model="",
|
|
1457
|
-
pipeline_mode="full",
|
|
1458
1651
|
checkpoint_path=execution_root / ".prizmkit" / "specs" / "001-support" / "workflow-checkpoint.json",
|
|
1459
1652
|
artifact_path=execution_root / ".prizmkit" / "specs" / "001-support",
|
|
1460
1653
|
raw={},
|
|
@@ -1541,7 +1734,6 @@ def test_has_branch_completion_evidence_requires_task_branch_ahead_of_base(tmp_p
|
|
|
1541
1734
|
subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=tmp_path, check=True)
|
|
1542
1735
|
subprocess.run(["git", "config", "user.name", "Test"], cwd=tmp_path, check=True)
|
|
1543
1736
|
artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-done"
|
|
1544
|
-
_write_semantic_completion_checkpoint(tmp_path, artifact_dir)
|
|
1545
1737
|
(tmp_path / "base.txt").write_text("base", encoding="utf-8")
|
|
1546
1738
|
subprocess.run(["git", "add", "base.txt"], cwd=tmp_path, check=True)
|
|
1547
1739
|
subprocess.run(["git", "commit", "-m", "base"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
|
|
@@ -1549,6 +1741,7 @@ def test_has_branch_completion_evidence_requires_task_branch_ahead_of_base(tmp_p
|
|
|
1549
1741
|
(tmp_path / "done.txt").write_text("done", encoding="utf-8")
|
|
1550
1742
|
subprocess.run(["git", "add", "done.txt"], cwd=tmp_path, check=True)
|
|
1551
1743
|
subprocess.run(["git", "commit", "-m", "done"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
|
|
1744
|
+
_write_semantic_completion_checkpoint(tmp_path, artifact_dir)
|
|
1552
1745
|
|
|
1553
1746
|
assert has_branch_completion_evidence(tmp_path, "main", "dev/F-001", artifact_dir) is True
|
|
1554
1747
|
|
|
@@ -1898,6 +2091,7 @@ def test_foreground_runner_emits_structured_feature_progress(monkeypatch, tmp_pa
|
|
|
1898
2091
|
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
1899
2092
|
|
|
1900
2093
|
monkeypatch.setenv("DEV_BRANCH", "dev/F-001-output")
|
|
2094
|
+
monkeypatch.delenv("MAX_RETRIES", raising=False)
|
|
1901
2095
|
paths = _make_paths(tmp_path)
|
|
1902
2096
|
family = family_for("feature", paths)
|
|
1903
2097
|
invocation = parse_invocation(family, "run", ())
|
|
@@ -1919,7 +2113,7 @@ def test_foreground_runner_emits_structured_feature_progress(monkeypatch, tmp_pa
|
|
|
1919
2113
|
assert "Feature: F-001 — Readable output" in captured.err
|
|
1920
2114
|
assert "Code retry: 1 / 3" in captured.err
|
|
1921
2115
|
assert "Preflight: no user-visible changes to preserve before feature task F-001" in captured.err
|
|
1922
|
-
assert "Pipeline mode:
|
|
2116
|
+
assert "Pipeline mode:" not in captured.err
|
|
1923
2117
|
assert "Spawning AI CLI session: F-001-session-1" in captured.err
|
|
1924
2118
|
assert "Session result: success" in captured.err
|
|
1925
2119
|
|
|
@@ -1976,6 +2170,8 @@ def test_no_worktree_interrupt_saves_wip_and_returns_to_base(monkeypatch, tmp_pa
|
|
|
1976
2170
|
from prizmkit_runtime.task_checkout import load_task_checkout
|
|
1977
2171
|
|
|
1978
2172
|
monkeypatch.setenv("DEV_BRANCH", "dev/F-001-interrupt")
|
|
2173
|
+
monkeypatch.delenv("MAX_RETRIES", raising=False)
|
|
2174
|
+
monkeypatch.delenv("MAX_INFRA_RETRIES", raising=False)
|
|
1979
2175
|
paths = _make_paths(tmp_path)
|
|
1980
2176
|
_init_bookkeeping_repo(paths.project_root)
|
|
1981
2177
|
family = family_for("feature", paths)
|
|
@@ -3089,6 +3285,14 @@ def test_classification_accepts_final_success_after_internal_stream_retry(tmp_pa
|
|
|
3089
3285
|
stdout=subprocess.PIPE,
|
|
3090
3286
|
text=True,
|
|
3091
3287
|
).stdout.strip()
|
|
3288
|
+
(tmp_path / "done.txt").write_text("done", encoding="utf-8")
|
|
3289
|
+
subprocess.run(["git", "add", "done.txt"], cwd=tmp_path, check=True)
|
|
3290
|
+
subprocess.run(
|
|
3291
|
+
["git", "commit", "-m", "done"],
|
|
3292
|
+
cwd=tmp_path,
|
|
3293
|
+
check=True,
|
|
3294
|
+
stdout=subprocess.DEVNULL,
|
|
3295
|
+
)
|
|
3092
3296
|
artifact_dir = tmp_path / ".prizmkit" / "bugfix" / "B-004"
|
|
3093
3297
|
checkpoint = _write_semantic_completion_checkpoint(tmp_path, artifact_dir, "bugfix-pipeline")
|
|
3094
3298
|
|
|
@@ -3236,7 +3440,7 @@ def test_classification_keeps_pre_completion_provider_error_as_infra_error(tmp_p
|
|
|
3236
3440
|
assert classification.reason == "ai_runtime_or_provider_error"
|
|
3237
3441
|
|
|
3238
3442
|
|
|
3239
|
-
def
|
|
3443
|
+
def test_classification_rejects_completed_checkpoint_without_runtime_git_receipt(tmp_path):
|
|
3240
3444
|
from prizmkit_runtime.runner_classification import classify_session
|
|
3241
3445
|
from prizmkit_runtime.runner_models import PromptGenerationResult
|
|
3242
3446
|
from prizmkit_runtime.sessions import AISessionCommand, AISessionResult
|
|
@@ -3276,8 +3480,8 @@ def test_classification_accepts_terminal_success_with_completed_checkpoint_witho
|
|
|
3276
3480
|
)
|
|
3277
3481
|
|
|
3278
3482
|
classification = classify_session(raw, project_root=tmp_path, base_head=base_head, prompt=prompt)
|
|
3279
|
-
assert classification.session_status == "
|
|
3280
|
-
assert classification.reason == "
|
|
3483
|
+
assert classification.session_status == "commit_missing"
|
|
3484
|
+
assert classification.reason == "runtime_commit_receipt_mismatch"
|
|
3281
3485
|
|
|
3282
3486
|
|
|
3283
3487
|
def test_classification_preserves_success_before_context_overflow(tmp_path):
|
|
@@ -3390,37 +3594,11 @@ def test_classification_stalls_after_repeated_no_progress_context_overflow(tmp_p
|
|
|
3390
3594
|
assert classification.no_progress_count == 2
|
|
3391
3595
|
|
|
3392
3596
|
|
|
3393
|
-
def test_recovery_prompt_keeps_existing_branch_instead_of_recovery_suffix():
|
|
3394
|
-
text = (PIPELINE_ROOT / "scripts" / "generate-recovery-prompt.py").read_text(encoding="utf-8")
|
|
3395
|
-
|
|
3396
|
-
assert "fix/{bug_id}-recovery" not in text
|
|
3397
|
-
assert "do not create an additional" in text
|
|
3398
|
-
assert "last-resort fallback" in text
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
def test_recovery_detector_resolver_prefers_installed_platform_asset(tmp_path):
|
|
3402
|
-
from prizmkit_runtime.paths import resolve_runtime_paths
|
|
3403
|
-
from prizmkit_runtime.runner_recovery import RecoveryDetectorResolver
|
|
3404
|
-
|
|
3405
|
-
project = tmp_path / "project"
|
|
3406
|
-
agents = project / ".agents" / "skills" / "recovery-workflow" / "scripts"
|
|
3407
|
-
claude = project / ".claude" / "command-assets" / "recovery-workflow" / "scripts"
|
|
3408
|
-
agents.mkdir(parents=True)
|
|
3409
|
-
claude.mkdir(parents=True)
|
|
3410
|
-
agent_detector = agents / "detect-recovery-state.py"
|
|
3411
|
-
claude_detector = claude / "detect-recovery-state.py"
|
|
3412
|
-
agent_detector.write_text("# agent", encoding="utf-8")
|
|
3413
|
-
claude_detector.write_text("# claude", encoding="utf-8")
|
|
3414
|
-
paths = resolve_runtime_paths(pipeline_root=PIPELINE_ROOT, project_root=project)
|
|
3415
|
-
|
|
3416
|
-
assert RecoveryDetectorResolver(paths).resolve() == agent_detector
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
3597
|
def test_cli_family_status_uses_python_dispatch_not_transition_description(tmp_path):
|
|
3420
3598
|
paths = _make_paths(tmp_path)
|
|
3421
3599
|
_write_feature_list(paths)
|
|
3422
3600
|
result = subprocess.run(
|
|
3423
|
-
[PYTHON_310_PLUS, str(CLI_PATH), "--project-root", str(paths.project_root), "
|
|
3601
|
+
[PYTHON_310_PLUS, str(CLI_PATH), "--project-root", str(paths.project_root), "status", "feature"],
|
|
3424
3602
|
cwd=REPO_ROOT,
|
|
3425
3603
|
stdout=subprocess.PIPE,
|
|
3426
3604
|
stderr=subprocess.PIPE,
|
|
@@ -3480,7 +3658,7 @@ def test_python_reset_warns_before_mutation_and_matches_snapshot_cleanup(tmp_pat
|
|
|
3480
3658
|
for kind, item_id in [("feature", "F-001"), ("bugfix", "B-001"), ("refactor", "R-001")]:
|
|
3481
3659
|
paths = _make_paths(tmp_path / kind)
|
|
3482
3660
|
_plan, _state_dir, status_file = _write_family_case(paths, kind, item_id)
|
|
3483
|
-
result = run_reset_command(kind, (item_id,), paths)
|
|
3661
|
+
result = run_reset_command(kind, (item_id, "--fresh-checkout"), paths)
|
|
3484
3662
|
output = result.render()
|
|
3485
3663
|
reset_marker = f"Resetting {item_id} status"
|
|
3486
3664
|
|
|
@@ -3522,7 +3700,7 @@ def test_python_reset_cleans_authoritative_custom_branch_before_reset_marker(mon
|
|
|
3522
3700
|
or SimpleNamespace(branch=branch, cleaned=True, deleted=False, returned_to=return_branch, warnings=()),
|
|
3523
3701
|
)
|
|
3524
3702
|
|
|
3525
|
-
result = run_reset_command("feature", ("F-001",), paths)
|
|
3703
|
+
result = run_reset_command("feature", ("F-001", "--fresh-checkout"), paths)
|
|
3526
3704
|
|
|
3527
3705
|
assert result.exit_code == 0
|
|
3528
3706
|
assert cleanup_calls == [(custom_branch, False)]
|
|
@@ -3554,7 +3732,7 @@ def test_python_reset_failure_keeps_authoritative_checkout_active(monkeypatch, t
|
|
|
3554
3732
|
),
|
|
3555
3733
|
)
|
|
3556
3734
|
|
|
3557
|
-
result = run_reset_command("feature", ("F-001",), paths)
|
|
3735
|
+
result = run_reset_command("feature", ("F-001", "--fresh-checkout"), paths)
|
|
3558
3736
|
|
|
3559
3737
|
assert result.exit_code == 1
|
|
3560
3738
|
assert load_task_checkout(family.state_dir, family.task_type, "F-001") == checkout
|
|
@@ -3674,7 +3852,7 @@ def test_python_reset_stalled_filter_selects_stalled_context_items(tmp_path):
|
|
|
3674
3852
|
data[{"feature": "features", "bugfix": "bugs", "refactor": "refactors"}[kind]][0]["status"] = "stalled_context_continuation"
|
|
3675
3853
|
plan.write_text(json.dumps(data), encoding="utf-8")
|
|
3676
3854
|
|
|
3677
|
-
result = run_reset_command(kind, ("--stalled",), paths)
|
|
3855
|
+
result = run_reset_command(kind, ("--stalled", "--fresh-checkout"), paths)
|
|
3678
3856
|
|
|
3679
3857
|
assert result.exit_code == 0
|
|
3680
3858
|
assert f"Reset: {item_id}" in result.render()
|
|
@@ -3707,6 +3885,7 @@ def test_python_daemon_progress_uses_list_status_without_runtime_files(tmp_path)
|
|
|
3707
3885
|
{"id": "B-001", "title": "Done", "status": "completed"},
|
|
3708
3886
|
{"id": "B-002", "title": "Failed", "status": "failed"},
|
|
3709
3887
|
{"id": "B-003", "title": "Needs Info", "status": "needs_info"},
|
|
3888
|
+
{"id": "B-004", "title": "Blocked", "status": "auto_skipped"},
|
|
3710
3889
|
]}),
|
|
3711
3890
|
encoding="utf-8",
|
|
3712
3891
|
)
|
|
@@ -3724,12 +3903,13 @@ def test_python_daemon_progress_uses_list_status_without_runtime_files(tmp_path)
|
|
|
3724
3903
|
|
|
3725
3904
|
summary = _progress_summary(family_for(kind, paths), plan)
|
|
3726
3905
|
|
|
3727
|
-
assert summary["total"] == 3
|
|
3906
|
+
assert summary["total"] == (4 if kind == "bugfix" else 3)
|
|
3728
3907
|
assert summary["completed"] == 1
|
|
3729
3908
|
assert summary["failed"] == 1
|
|
3730
|
-
assert summary["percent"] == 33.3
|
|
3909
|
+
assert summary["percent"] == (25.0 if kind == "bugfix" else 33.3)
|
|
3731
3910
|
if kind == "bugfix":
|
|
3732
3911
|
assert summary["needs_info"] == 1
|
|
3912
|
+
assert summary["auto_skipped"] == 1
|
|
3733
3913
|
else:
|
|
3734
3914
|
assert summary["auto_skipped"] == 1
|
|
3735
3915
|
|
|
@@ -3862,6 +4042,14 @@ def test_pi_length_stop_does_not_override_completed_checkpoint(tmp_path):
|
|
|
3862
4042
|
from prizmkit_runtime.status import ProgressSummary
|
|
3863
4043
|
|
|
3864
4044
|
base_head = _init_git_repo_for_invalid_checkpoint_test(tmp_path)
|
|
4045
|
+
(tmp_path / "done.txt").write_text("done", encoding="utf-8")
|
|
4046
|
+
subprocess.run(["git", "add", "done.txt"], cwd=tmp_path, check=True)
|
|
4047
|
+
subprocess.run(
|
|
4048
|
+
["git", "commit", "-m", "done"],
|
|
4049
|
+
cwd=tmp_path,
|
|
4050
|
+
check=True,
|
|
4051
|
+
stdout=subprocess.DEVNULL,
|
|
4052
|
+
)
|
|
3865
4053
|
artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-length-complete"
|
|
3866
4054
|
checkpoint = _write_semantic_completion_checkpoint(tmp_path, artifact_dir)
|
|
3867
4055
|
prompt = PromptGenerationResult(
|
|
@@ -4308,6 +4496,21 @@ def test_semantic_completion_classification_has_cross_family_parity(tmp_path, wo
|
|
|
4308
4496
|
(tmp_path / "base.txt").write_text("base", encoding="utf-8")
|
|
4309
4497
|
subprocess.run(["git", "add", "base.txt"], cwd=tmp_path, check=True)
|
|
4310
4498
|
subprocess.run(["git", "commit", "-m", "base"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
|
|
4499
|
+
base_head = subprocess.run(
|
|
4500
|
+
["git", "rev-parse", "HEAD"],
|
|
4501
|
+
cwd=tmp_path,
|
|
4502
|
+
check=True,
|
|
4503
|
+
stdout=subprocess.PIPE,
|
|
4504
|
+
text=True,
|
|
4505
|
+
).stdout.strip()
|
|
4506
|
+
(tmp_path / "done.txt").write_text("done", encoding="utf-8")
|
|
4507
|
+
subprocess.run(["git", "add", "done.txt"], cwd=tmp_path, check=True)
|
|
4508
|
+
subprocess.run(
|
|
4509
|
+
["git", "commit", "-m", "done"],
|
|
4510
|
+
cwd=tmp_path,
|
|
4511
|
+
check=True,
|
|
4512
|
+
stdout=subprocess.DEVNULL,
|
|
4513
|
+
)
|
|
4311
4514
|
artifact = tmp_path / ".prizmkit" / "artifacts" / workflow_type
|
|
4312
4515
|
checkpoint = _write_semantic_completion_checkpoint(tmp_path, artifact, workflow_type)
|
|
4313
4516
|
log = tmp_path / f"{workflow_type}.log"
|
|
@@ -4330,7 +4533,7 @@ def test_semantic_completion_classification_has_cross_family_parity(tmp_path, wo
|
|
|
4330
4533
|
classification = classify_session(
|
|
4331
4534
|
raw,
|
|
4332
4535
|
project_root=tmp_path,
|
|
4333
|
-
base_head=
|
|
4536
|
+
base_head=base_head,
|
|
4334
4537
|
prompt=PromptGenerationResult(
|
|
4335
4538
|
output_path=tmp_path / "prompt.md",
|
|
4336
4539
|
checkpoint_path=checkpoint,
|
|
@@ -4396,38 +4599,10 @@ def test_semantic_blocked_classification_is_terminal_and_retry_neutral(tmp_path)
|
|
|
4396
4599
|
assert classification.checkpoint_state["repair_round"] == 3
|
|
4397
4600
|
|
|
4398
4601
|
|
|
4399
|
-
def test_recovery_outcome_preserves_dirty_partial_work_and_uses_semantic_checkpoint(tmp_path):
|
|
4400
|
-
from prizmkit_runtime.runner_recovery import _recovery_outcome
|
|
4401
|
-
|
|
4402
|
-
subprocess.run(["git", "init", "-b", "main"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
|
|
4403
|
-
subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=tmp_path, check=True)
|
|
4404
|
-
subprocess.run(["git", "config", "user.name", "Test"], cwd=tmp_path, check=True)
|
|
4405
|
-
(tmp_path / "base.txt").write_text("base", encoding="utf-8")
|
|
4406
|
-
subprocess.run(["git", "add", "base.txt"], cwd=tmp_path, check=True)
|
|
4407
|
-
subprocess.run(["git", "commit", "-m", "base"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
|
|
4408
|
-
(tmp_path / "partial.txt").write_text("preserve me", encoding="utf-8")
|
|
4409
|
-
artifact = tmp_path / ".prizmkit" / "specs" / "done"
|
|
4410
|
-
checkpoint = _write_semantic_completion_checkpoint(tmp_path, artifact)
|
|
4411
|
-
|
|
4412
|
-
outcome, semantic = _recovery_outcome(tmp_path, 1, checkpoint_path=checkpoint)
|
|
4413
|
-
|
|
4414
|
-
assert outcome == "success"
|
|
4415
|
-
assert semantic["terminal_status"] == "WORKFLOW_COMPLETED"
|
|
4416
|
-
assert (tmp_path / "partial.txt").read_text(encoding="utf-8") == "preserve me"
|
|
4417
|
-
assert subprocess.run(
|
|
4418
|
-
["git", "status", "--porcelain", "--", "partial.txt"],
|
|
4419
|
-
cwd=tmp_path,
|
|
4420
|
-
check=True,
|
|
4421
|
-
stdout=subprocess.PIPE,
|
|
4422
|
-
text=True,
|
|
4423
|
-
).stdout.startswith("??")
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
4602
|
def test_runtime_semantic_paths_do_not_invoke_composite_workflow():
|
|
4427
4603
|
sources = [
|
|
4428
4604
|
PIPELINE_ROOT / "prizmkit_runtime" / "checkpoint_state.py",
|
|
4429
4605
|
PIPELINE_ROOT / "prizmkit_runtime" / "runner_classification.py",
|
|
4430
|
-
PIPELINE_ROOT / "prizmkit_runtime" / "runner_recovery.py",
|
|
4431
4606
|
PIPELINE_ROOT / "prizmkit_runtime" / "runners.py",
|
|
4432
4607
|
]
|
|
4433
4608
|
|
|
@@ -4494,110 +4669,10 @@ def test_family_status_persists_identical_semantic_blocked_snapshot_without_retr
|
|
|
4494
4669
|
assert status[key] == value
|
|
4495
4670
|
|
|
4496
4671
|
|
|
4497
|
-
|
|
4498
|
-
def test_recovery_detector_returns_no_candidate_for_empty_project(tmp_path):
|
|
4499
|
-
import importlib.util
|
|
4500
|
-
|
|
4501
|
-
detector_path = (
|
|
4502
|
-
REPO_ROOT / "core" / "skills" / "orchestration-skill" / "workflows"
|
|
4503
|
-
/ "recovery-workflow" / "scripts" / "detect-recovery-state.py"
|
|
4504
|
-
)
|
|
4505
|
-
spec = importlib.util.spec_from_file_location("recovery_detector_empty", detector_path)
|
|
4506
|
-
module = importlib.util.module_from_spec(spec)
|
|
4507
|
-
spec.loader.exec_module(module)
|
|
4508
|
-
|
|
4509
|
-
report = module.build_report(tmp_path)
|
|
4510
|
-
|
|
4511
|
-
assert report["detected"] is False
|
|
4512
|
-
assert report["candidates"] == []
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
def test_recovery_detector_returns_active_checkout_command(tmp_path, monkeypatch):
|
|
4516
|
-
import importlib.util
|
|
4517
|
-
|
|
4518
|
-
detector_path = (
|
|
4519
|
-
REPO_ROOT / "core" / "skills" / "orchestration-skill" / "workflows"
|
|
4520
|
-
/ "recovery-workflow" / "scripts" / "detect-recovery-state.py"
|
|
4521
|
-
)
|
|
4522
|
-
spec = importlib.util.spec_from_file_location("recovery_detector_active", detector_path)
|
|
4523
|
-
module = importlib.util.module_from_spec(spec)
|
|
4524
|
-
spec.loader.exec_module(module)
|
|
4525
|
-
|
|
4526
|
-
checkout_path = tmp_path / ".prizmkit" / "state" / "features" / "F-001" / "checkout.json"
|
|
4527
|
-
checkout_path.parent.mkdir(parents=True)
|
|
4528
|
-
checkout_path.write_text(
|
|
4529
|
-
json.dumps({
|
|
4530
|
-
"task_type": "feature",
|
|
4531
|
-
"task_id": "F-001",
|
|
4532
|
-
"state": "active",
|
|
4533
|
-
"active_dev_branch": "dev/F-001-resume",
|
|
4534
|
-
"base_branch": "main",
|
|
4535
|
-
"checkout_mode": "branch",
|
|
4536
|
-
}),
|
|
4537
|
-
encoding="utf-8",
|
|
4538
|
-
)
|
|
4539
|
-
monkeypatch.setattr(module, "branch_exists", lambda *_args: True)
|
|
4540
|
-
|
|
4541
|
-
report = module.build_report(tmp_path)
|
|
4542
|
-
|
|
4543
|
-
assert report["detected"] is True
|
|
4544
|
-
assert report["selection_required"] is False
|
|
4545
|
-
assert report["candidates"][0]["continue_command"] == (
|
|
4546
|
-
"python3 ./.prizmkit/dev-pipeline/cli.py feature run F-001"
|
|
4547
|
-
)
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
def test_recovery_detector_ignores_completed_checkout(tmp_path, monkeypatch):
|
|
4551
|
-
import importlib.util
|
|
4552
|
-
|
|
4553
|
-
detector_path = (
|
|
4554
|
-
REPO_ROOT / "core" / "skills" / "orchestration-skill" / "workflows"
|
|
4555
|
-
/ "recovery-workflow" / "scripts" / "detect-recovery-state.py"
|
|
4556
|
-
)
|
|
4557
|
-
spec = importlib.util.spec_from_file_location("recovery_detector_completed", detector_path)
|
|
4558
|
-
module = importlib.util.module_from_spec(spec)
|
|
4559
|
-
spec.loader.exec_module(module)
|
|
4560
|
-
|
|
4561
|
-
checkout_path = tmp_path / ".prizmkit" / "state" / "bugfix" / "B-001" / "checkout.json"
|
|
4562
|
-
checkout_path.parent.mkdir(parents=True)
|
|
4563
|
-
checkout_path.write_text(
|
|
4564
|
-
json.dumps({
|
|
4565
|
-
"task_type": "bugfix",
|
|
4566
|
-
"task_id": "B-001",
|
|
4567
|
-
"state": "completed",
|
|
4568
|
-
"active_dev_branch": "bugfix/B-001-done",
|
|
4569
|
-
"base_branch": "main",
|
|
4570
|
-
}),
|
|
4571
|
-
encoding="utf-8",
|
|
4572
|
-
)
|
|
4573
|
-
monkeypatch.setattr(module, "branch_exists", lambda *_args: True)
|
|
4574
|
-
|
|
4575
|
-
report = module.build_report(tmp_path)
|
|
4576
|
-
|
|
4577
|
-
assert report["detected"] is False
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
def test_l4_runtime_and_recovery_sources_do_not_depend_on_direct_l1_composite():
|
|
4672
|
+
def test_runtime_sources_do_not_depend_on_direct_lifecycle_composite():
|
|
4581
4673
|
runtime_root = REPO_ROOT / "dev-pipeline"
|
|
4582
4674
|
source_files = list(runtime_root.rglob("*.py"))
|
|
4583
4675
|
for source in source_files:
|
|
4584
4676
|
if "__pycache__" in source.parts or "tests" in source.parts or not source.is_file():
|
|
4585
4677
|
continue
|
|
4586
4678
|
assert "prizmkit-workflow" not in source.read_text(encoding="utf-8")
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
def test_recovery_detector_has_no_direct_lifecycle_fallback(tmp_path):
|
|
4590
|
-
import importlib.util
|
|
4591
|
-
|
|
4592
|
-
detector_path = (
|
|
4593
|
-
REPO_ROOT / "core" / "skills" / "orchestration-skill" / "workflows"
|
|
4594
|
-
/ "recovery-workflow" / "scripts" / "detect-recovery-state.py"
|
|
4595
|
-
)
|
|
4596
|
-
spec = importlib.util.spec_from_file_location("recovery_detector_contract", detector_path)
|
|
4597
|
-
module = importlib.util.module_from_spec(spec)
|
|
4598
|
-
spec.loader.exec_module(module)
|
|
4599
|
-
|
|
4600
|
-
report = module.build_report(tmp_path)
|
|
4601
|
-
|
|
4602
|
-
assert "continue_command" not in report
|
|
4603
|
-
assert report["detected"] is False
|