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
|
@@ -6,15 +6,19 @@ import copy
|
|
|
6
6
|
import json
|
|
7
7
|
import re
|
|
8
8
|
from dataclasses import dataclass, field
|
|
9
|
-
from pathlib import Path
|
|
9
|
+
from pathlib import Path, PurePosixPath
|
|
10
10
|
from typing import Any, Mapping
|
|
11
11
|
|
|
12
|
-
from .test_result import
|
|
12
|
+
from .test_result import (
|
|
13
|
+
valid_legacy_test_artifact_pair,
|
|
14
|
+
valid_test_artifact_pair,
|
|
15
|
+
)
|
|
13
16
|
|
|
14
17
|
VALID_CHECKPOINT_STATUSES = {"pending", "in_progress", "completed", "skipped", "failed"}
|
|
15
18
|
TERMINAL_STEP_STATUSES = {"completed", "skipped"}
|
|
16
19
|
STEP_ID_PATTERN = re.compile(r"^S[0-9]+$")
|
|
17
20
|
MAX_OUTER_REPAIR_ROUNDS = 3
|
|
21
|
+
_COMMIT_HASH_PATTERN = re.compile(r"^[0-9a-fA-F]{40,64}$")
|
|
18
22
|
|
|
19
23
|
REVIEW_RESULTS = {"REVIEW_PASS", "REVIEW_NEEDS_FIXES"}
|
|
20
24
|
TEST_RESULTS = {"TEST_PASS", "TEST_NEEDS_FIXES", "TEST_BLOCKED"}
|
|
@@ -32,6 +36,8 @@ VALID_STAGE_RESULTS = (
|
|
|
32
36
|
)
|
|
33
37
|
STAGE_RESULT_OWNERS = {
|
|
34
38
|
"prizmkit-plan": PLAN_RESULTS,
|
|
39
|
+
"context-snapshot-and-plan": PLAN_RESULTS,
|
|
40
|
+
"bug-diagnosis-and-plan": PLAN_RESULTS,
|
|
35
41
|
"prizmkit-implement": IMPLEMENT_RESULTS,
|
|
36
42
|
"prizmkit-code-review": REVIEW_RESULTS,
|
|
37
43
|
"prizmkit-test": TEST_RESULTS,
|
|
@@ -49,6 +55,15 @@ WORKFLOW_STAGE_SKILLS = {
|
|
|
49
55
|
SKILL_WORKFLOW_STAGES = {
|
|
50
56
|
skill: stage for stage, skill in WORKFLOW_STAGE_SKILLS.items()
|
|
51
57
|
}
|
|
58
|
+
SKILL_WORKFLOW_STAGES.update({
|
|
59
|
+
"context-snapshot-and-plan": "plan",
|
|
60
|
+
"bug-diagnosis-and-plan": "plan",
|
|
61
|
+
})
|
|
62
|
+
FAMILY_PLAN_SKILLS = {
|
|
63
|
+
"feature-pipeline": "context-snapshot-and-plan",
|
|
64
|
+
"bugfix-pipeline": "bug-diagnosis-and-plan",
|
|
65
|
+
"refactor-pipeline": "prizmkit-plan",
|
|
66
|
+
}
|
|
52
67
|
VALID_TERMINAL_STATUSES = {"WORKFLOW_COMPLETED", "WORKFLOW_BLOCKED"}
|
|
53
68
|
VALID_REPAIR_SCOPES = {
|
|
54
69
|
"production",
|
|
@@ -63,7 +78,7 @@ SEMANTIC_STATE_KEYS = {
|
|
|
63
78
|
"bugfix-pipeline": "bugfix_state",
|
|
64
79
|
"refactor-pipeline": "refactor_state",
|
|
65
80
|
}
|
|
66
|
-
|
|
81
|
+
LEGACY_FAMILY_REQUIRED_STAGES = {
|
|
67
82
|
"feature-pipeline": (
|
|
68
83
|
"prizmkit-implement",
|
|
69
84
|
"prizmkit-code-review",
|
|
@@ -91,6 +106,34 @@ FAMILY_REQUIRED_STAGES = {
|
|
|
91
106
|
),
|
|
92
107
|
}
|
|
93
108
|
|
|
109
|
+
FAMILY_REQUIRED_STAGES = {
|
|
110
|
+
"feature-pipeline": (
|
|
111
|
+
"prizmkit-implement",
|
|
112
|
+
"prizmkit-code-review",
|
|
113
|
+
"prizmkit-test",
|
|
114
|
+
"completion-summary",
|
|
115
|
+
"prizmkit-retrospective",
|
|
116
|
+
"prizmkit-committer",
|
|
117
|
+
),
|
|
118
|
+
"bugfix-pipeline": (
|
|
119
|
+
"prizmkit-implement",
|
|
120
|
+
"prizmkit-code-review",
|
|
121
|
+
"prizmkit-test",
|
|
122
|
+
"bug-report",
|
|
123
|
+
"prizmkit-retrospective",
|
|
124
|
+
"prizmkit-committer",
|
|
125
|
+
),
|
|
126
|
+
"refactor-pipeline": (
|
|
127
|
+
"prizmkit-implement",
|
|
128
|
+
"prizmkit-code-review",
|
|
129
|
+
"prizmkit-test",
|
|
130
|
+
"refactor-report",
|
|
131
|
+
"completion-summary",
|
|
132
|
+
"prizmkit-retrospective",
|
|
133
|
+
"prizmkit-committer",
|
|
134
|
+
),
|
|
135
|
+
}
|
|
136
|
+
|
|
94
137
|
|
|
95
138
|
@dataclass(frozen=True)
|
|
96
139
|
class CheckpointStep:
|
|
@@ -128,10 +171,13 @@ class SemanticCheckpoint:
|
|
|
128
171
|
repair_round: int = 0
|
|
129
172
|
blocked_reason: str | None = None
|
|
130
173
|
terminal_status: str | None = None
|
|
174
|
+
runtime_base_head: str = ""
|
|
175
|
+
runtime_commit_hash: str = ""
|
|
131
176
|
sequence_valid: bool = False
|
|
132
177
|
test_result_valid: bool = False
|
|
133
178
|
artifacts_valid: bool = False
|
|
134
179
|
completion_ready: bool = False
|
|
180
|
+
commit_ready: bool = False
|
|
135
181
|
complete: bool = False
|
|
136
182
|
blocked: bool = False
|
|
137
183
|
error_code: str = ""
|
|
@@ -147,6 +193,9 @@ class SemanticCheckpoint:
|
|
|
147
193
|
"repair_round": self.repair_round,
|
|
148
194
|
"blocked_reason": self.blocked_reason,
|
|
149
195
|
"terminal_status": self.terminal_status,
|
|
196
|
+
"runtime_base_head": self.runtime_base_head,
|
|
197
|
+
"runtime_commit_hash": self.runtime_commit_hash,
|
|
198
|
+
"commit_ready": self.commit_ready,
|
|
150
199
|
}
|
|
151
200
|
|
|
152
201
|
|
|
@@ -200,6 +249,11 @@ class CheckpointState:
|
|
|
200
249
|
"""Return whether this checkpoint authoritatively proves task completion."""
|
|
201
250
|
return self.valid and self.semantic.complete
|
|
202
251
|
|
|
252
|
+
@property
|
|
253
|
+
def semantic_commit_ready(self) -> bool:
|
|
254
|
+
"""Return whether the runtime may execute the prepared local commit."""
|
|
255
|
+
return self.valid and self.semantic.commit_ready
|
|
256
|
+
|
|
203
257
|
@property
|
|
204
258
|
def semantic_blocked(self) -> bool:
|
|
205
259
|
"""Return whether this checkpoint has a deterministic blocked terminal state."""
|
|
@@ -591,8 +645,21 @@ def _derive_semantic_state(
|
|
|
591
645
|
return _semantic_error(workflow_type, current_step, "invalid_semantic_state", f"{state_key} must be an object")
|
|
592
646
|
|
|
593
647
|
required = FAMILY_REQUIRED_STAGES.get(workflow_type)
|
|
594
|
-
|
|
648
|
+
canonical_sequence_error = (
|
|
649
|
+
_required_sequence_error(steps, required)
|
|
650
|
+
if required else "unsupported workflow family"
|
|
651
|
+
)
|
|
652
|
+
if not canonical_sequence_error:
|
|
653
|
+
canonical_sequence_error = _configured_verification_sequence_error(steps)
|
|
654
|
+
legacy_required = LEGACY_FAMILY_REQUIRED_STAGES.get(workflow_type)
|
|
655
|
+
legacy_sequence_valid = bool(
|
|
656
|
+
canonical_sequence_error
|
|
657
|
+
and legacy_required
|
|
658
|
+
and not _required_sequence_error(steps, legacy_required)
|
|
659
|
+
)
|
|
660
|
+
sequence_error = "" if legacy_sequence_valid else canonical_sequence_error
|
|
595
661
|
sequence_valid = not sequence_error
|
|
662
|
+
canonical_sequence_valid = not canonical_sequence_error
|
|
596
663
|
|
|
597
664
|
repair_round = raw_state.get("repair_round", 0)
|
|
598
665
|
if isinstance(repair_round, bool) or not isinstance(repair_round, int) or not 0 <= repair_round <= MAX_OUTER_REPAIR_ROUNDS:
|
|
@@ -629,14 +696,61 @@ def _derive_semantic_state(
|
|
|
629
696
|
terminal_status = raw_state.get("terminal_status")
|
|
630
697
|
if terminal_status is not None and terminal_status not in VALID_TERMINAL_STATUSES:
|
|
631
698
|
return _semantic_error(workflow_type, current_step, "invalid_terminal_status", "terminal_status is not recognized")
|
|
699
|
+
runtime_base_head = str(raw_state.get("runtime_base_head") or "").strip()
|
|
700
|
+
runtime_commit_hash = str(raw_state.get("runtime_commit_hash") or "").strip()
|
|
701
|
+
runtime_receipt_valid = bool(
|
|
702
|
+
_COMMIT_HASH_PATTERN.fullmatch(runtime_base_head)
|
|
703
|
+
and _COMMIT_HASH_PATTERN.fullmatch(runtime_commit_hash)
|
|
704
|
+
and runtime_base_head.lower() != runtime_commit_hash.lower()
|
|
705
|
+
)
|
|
706
|
+
if (runtime_base_head or runtime_commit_hash) and not runtime_receipt_valid:
|
|
707
|
+
return _semantic_error(
|
|
708
|
+
workflow_type,
|
|
709
|
+
current_step,
|
|
710
|
+
"invalid_runtime_commit_receipt",
|
|
711
|
+
"Runtime commit receipt requires distinct valid base and commit hashes",
|
|
712
|
+
)
|
|
632
713
|
blocked_reason = raw_state.get("blocked_reason")
|
|
633
714
|
if blocked_reason is not None and (not isinstance(blocked_reason, str) or not blocked_reason.strip()):
|
|
634
715
|
return _semantic_error(workflow_type, current_step, "invalid_blocked_reason", "blocked_reason must be a non-empty string")
|
|
635
716
|
if terminal_status == "WORKFLOW_BLOCKED" and not blocked_reason:
|
|
636
717
|
return _semantic_error(workflow_type, current_step, "missing_blocked_reason", "WORKFLOW_BLOCKED requires blocked_reason")
|
|
718
|
+
if blocked_reason and terminal_status == "WORKFLOW_COMPLETED":
|
|
719
|
+
return _semantic_error(
|
|
720
|
+
workflow_type,
|
|
721
|
+
current_step,
|
|
722
|
+
"completed_workflow_has_blocked_reason",
|
|
723
|
+
"WORKFLOW_COMPLETED cannot retain blocked_reason",
|
|
724
|
+
)
|
|
725
|
+
if blocked_reason and terminal_status != "WORKFLOW_BLOCKED":
|
|
726
|
+
return _semantic_error(
|
|
727
|
+
workflow_type,
|
|
728
|
+
current_step,
|
|
729
|
+
"blocked_reason_without_blocked_terminal",
|
|
730
|
+
"blocked_reason requires terminal_status=WORKFLOW_BLOCKED",
|
|
731
|
+
)
|
|
637
732
|
|
|
638
733
|
review_step = _single_step(steps, "prizmkit-code-review")
|
|
639
734
|
test_step = _single_step(steps, "prizmkit-test")
|
|
735
|
+
plan_step = _single_step(steps, FAMILY_PLAN_SKILLS.get(workflow_type, ""))
|
|
736
|
+
implement_step = _single_step(steps, "prizmkit-implement")
|
|
737
|
+
plan_valid = bool(
|
|
738
|
+
plan_step
|
|
739
|
+
and implement_step
|
|
740
|
+
and plan_step.index < implement_step.index
|
|
741
|
+
and plan_step.status == "completed"
|
|
742
|
+
and plan_step.stage_result == "PLAN_READY"
|
|
743
|
+
)
|
|
744
|
+
configured_manual_valid = all(
|
|
745
|
+
step.status == "completed"
|
|
746
|
+
for step in steps
|
|
747
|
+
if step.skill == "manual-verification"
|
|
748
|
+
)
|
|
749
|
+
implementation_valid = bool(
|
|
750
|
+
implement_step
|
|
751
|
+
and implement_step.status == "completed"
|
|
752
|
+
and implement_step.stage_result == "IMPLEMENTED"
|
|
753
|
+
)
|
|
640
754
|
review_valid = bool(
|
|
641
755
|
review_step
|
|
642
756
|
and review_step.status == "completed"
|
|
@@ -647,18 +761,65 @@ def _derive_semantic_state(
|
|
|
647
761
|
and test_step.status == "completed"
|
|
648
762
|
and test_step.stage_result == "TEST_PASS"
|
|
649
763
|
)
|
|
650
|
-
|
|
764
|
+
effective_required = legacy_required if legacy_sequence_valid else required
|
|
765
|
+
all_required_completed = bool(effective_required) and all(
|
|
651
766
|
(step := _single_step(steps, skill)) is not None and step.status == "completed"
|
|
652
|
-
for skill in
|
|
767
|
+
for skill in effective_required
|
|
653
768
|
)
|
|
654
769
|
artifacts_valid = all(
|
|
655
770
|
_required_step_artifacts_valid(step, checkpoint_path, project_root)
|
|
656
771
|
for step in steps
|
|
657
772
|
if step.status == "completed"
|
|
658
773
|
)
|
|
774
|
+
completion_artifacts_valid = artifacts_valid or (
|
|
775
|
+
legacy_sequence_valid
|
|
776
|
+
and all(
|
|
777
|
+
_required_step_artifacts_valid(
|
|
778
|
+
step,
|
|
779
|
+
checkpoint_path,
|
|
780
|
+
project_root,
|
|
781
|
+
allow_legacy_test=True,
|
|
782
|
+
)
|
|
783
|
+
for step in steps
|
|
784
|
+
if step.status == "completed"
|
|
785
|
+
)
|
|
786
|
+
)
|
|
787
|
+
retrospective_step = _single_step(steps, "prizmkit-retrospective")
|
|
788
|
+
retrospective_valid = bool(
|
|
789
|
+
retrospective_step
|
|
790
|
+
and retrospective_step.status == "completed"
|
|
791
|
+
and retrospective_step.stage_result == "RETRO_COMPLETE"
|
|
792
|
+
)
|
|
793
|
+
committer_step = _single_step(steps, "prizmkit-committer")
|
|
794
|
+
committed_valid = bool(
|
|
795
|
+
committer_step
|
|
796
|
+
and committer_step.status == "completed"
|
|
797
|
+
and committer_step.stage_result == "COMMITTED"
|
|
798
|
+
)
|
|
799
|
+
precommit_required = tuple(
|
|
800
|
+
skill for skill in (required or ()) if skill != "prizmkit-committer"
|
|
801
|
+
)
|
|
802
|
+
all_precommit_completed = bool(precommit_required) and all(
|
|
803
|
+
(step := _single_step(steps, skill)) is not None and step.status == "completed"
|
|
804
|
+
for skill in precommit_required
|
|
805
|
+
)
|
|
806
|
+
commit_request_valid = bool(
|
|
807
|
+
committer_step
|
|
808
|
+
and committer_step.status == "in_progress"
|
|
809
|
+
and committer_step.stage_result == "COMMIT_PENDING"
|
|
810
|
+
and _required_step_artifacts_valid(committer_step, checkpoint_path, project_root)
|
|
811
|
+
)
|
|
812
|
+
commit_ready = (
|
|
813
|
+
canonical_sequence_valid and plan_valid and configured_manual_valid
|
|
814
|
+
and all_precommit_completed and implementation_valid and review_valid
|
|
815
|
+
and test_result_valid and retrospective_valid and artifacts_valid
|
|
816
|
+
and commit_request_valid and terminal_status is None
|
|
817
|
+
)
|
|
659
818
|
completion_ready = (
|
|
660
|
-
sequence_valid and all_required_completed and
|
|
661
|
-
and test_result_valid and
|
|
819
|
+
sequence_valid and all_required_completed and implementation_valid
|
|
820
|
+
and review_valid and test_result_valid and retrospective_valid
|
|
821
|
+
and committed_valid and completion_artifacts_valid
|
|
822
|
+
and (legacy_sequence_valid or runtime_receipt_valid)
|
|
662
823
|
)
|
|
663
824
|
blocked = terminal_status == "WORKFLOW_BLOCKED" and bool(blocked_reason)
|
|
664
825
|
complete = completion_ready and terminal_status == "WORKFLOW_COMPLETED" and not blocked
|
|
@@ -705,10 +866,13 @@ def _derive_semantic_state(
|
|
|
705
866
|
repair_round=repair_round,
|
|
706
867
|
blocked_reason=blocked_reason,
|
|
707
868
|
terminal_status=terminal_status,
|
|
869
|
+
runtime_base_head=runtime_base_head,
|
|
870
|
+
runtime_commit_hash=runtime_commit_hash,
|
|
708
871
|
sequence_valid=sequence_valid,
|
|
709
872
|
test_result_valid=test_result_valid,
|
|
710
|
-
artifacts_valid=
|
|
873
|
+
artifacts_valid=completion_artifacts_valid,
|
|
711
874
|
completion_ready=completion_ready,
|
|
875
|
+
commit_ready=commit_ready,
|
|
712
876
|
complete=complete,
|
|
713
877
|
blocked=blocked,
|
|
714
878
|
error_code=error_code,
|
|
@@ -733,16 +897,34 @@ def _required_sequence_error(
|
|
|
733
897
|
return ""
|
|
734
898
|
|
|
735
899
|
|
|
900
|
+
def _configured_verification_sequence_error(
|
|
901
|
+
steps: tuple[CheckpointStep, ...],
|
|
902
|
+
) -> str:
|
|
903
|
+
"""Require configured Browser/Manual checks between Review and Test."""
|
|
904
|
+
review = _single_step(steps, "prizmkit-code-review")
|
|
905
|
+
test = _single_step(steps, "prizmkit-test")
|
|
906
|
+
if review is None or test is None:
|
|
907
|
+
return ""
|
|
908
|
+
for skill in ("browser-verification", "manual-verification"):
|
|
909
|
+
matches = [step for step in steps if step.skill == skill]
|
|
910
|
+
if len(matches) > 1:
|
|
911
|
+
return f"Expected at most one configured stage {skill}"
|
|
912
|
+
if matches and not (review.index < matches[0].index < test.index):
|
|
913
|
+
return f"Configured stage {skill} must run after review and before test"
|
|
914
|
+
return ""
|
|
915
|
+
|
|
916
|
+
|
|
736
917
|
def _single_step(steps: tuple[CheckpointStep, ...], skill: str) -> CheckpointStep | None:
|
|
737
918
|
matches = [step for step in steps if step.skill == skill]
|
|
738
919
|
return matches[0] if len(matches) == 1 else None
|
|
739
920
|
|
|
740
921
|
|
|
741
|
-
|
|
742
922
|
def _required_step_artifacts_valid(
|
|
743
923
|
step: CheckpointStep,
|
|
744
924
|
checkpoint_path: Path | None,
|
|
745
925
|
project_root: Path | None,
|
|
926
|
+
*,
|
|
927
|
+
allow_legacy_test: bool = False,
|
|
746
928
|
) -> bool:
|
|
747
929
|
"""Validate declared artifacts, including semantic test terminal output."""
|
|
748
930
|
if step.skill == "prizmkit-test":
|
|
@@ -755,11 +937,38 @@ def _required_step_artifacts_valid(
|
|
|
755
937
|
result_path = (root / results[0]).resolve()
|
|
756
938
|
if not _is_within_root(report_path, root) or not _is_within_root(result_path, root):
|
|
757
939
|
return False
|
|
758
|
-
|
|
940
|
+
validator = (
|
|
941
|
+
valid_legacy_test_artifact_pair
|
|
942
|
+
if allow_legacy_test
|
|
943
|
+
else valid_test_artifact_pair
|
|
944
|
+
)
|
|
945
|
+
return validator(
|
|
759
946
|
report_path,
|
|
760
947
|
result_path,
|
|
761
948
|
required_result="TEST_PASS",
|
|
762
949
|
)
|
|
950
|
+
if step.skill == "prizmkit-code-review":
|
|
951
|
+
return _valid_review_artifact(step, checkpoint_path, project_root)
|
|
952
|
+
if step.skill == "prizmkit-retrospective":
|
|
953
|
+
return _valid_retrospective_artifact(step, checkpoint_path, project_root)
|
|
954
|
+
if (
|
|
955
|
+
step.skill == "prizmkit-committer"
|
|
956
|
+
and step.status == "completed"
|
|
957
|
+
and step.stage_result == "COMMITTED"
|
|
958
|
+
):
|
|
959
|
+
requests = tuple(
|
|
960
|
+
artifact
|
|
961
|
+
for artifact in step.required_artifacts
|
|
962
|
+
if artifact.endswith("/runtime-commit-request.json")
|
|
963
|
+
)
|
|
964
|
+
if not step.required_artifacts:
|
|
965
|
+
return True
|
|
966
|
+
if len(requests) != 1:
|
|
967
|
+
return False
|
|
968
|
+
durable = tuple(
|
|
969
|
+
artifact for artifact in step.required_artifacts if artifact not in requests
|
|
970
|
+
)
|
|
971
|
+
return _required_artifact_paths_exist(durable, checkpoint_path, project_root)
|
|
763
972
|
return _required_artifacts_exist(step, checkpoint_path, project_root)
|
|
764
973
|
|
|
765
974
|
|
|
@@ -768,23 +977,154 @@ def _required_artifacts_exist(
|
|
|
768
977
|
checkpoint_path: Path | None,
|
|
769
978
|
project_root: Path | None,
|
|
770
979
|
) -> bool:
|
|
771
|
-
|
|
980
|
+
return _required_artifact_paths_exist(
|
|
981
|
+
step.required_artifacts,
|
|
982
|
+
checkpoint_path,
|
|
983
|
+
project_root,
|
|
984
|
+
)
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
def _required_artifact_paths_exist(
|
|
988
|
+
artifacts: tuple[str, ...],
|
|
989
|
+
checkpoint_path: Path | None,
|
|
990
|
+
project_root: Path | None,
|
|
991
|
+
) -> bool:
|
|
992
|
+
if not artifacts:
|
|
772
993
|
return True
|
|
773
994
|
import glob
|
|
774
995
|
|
|
775
996
|
root = (project_root or _infer_project_root(checkpoint_path)).resolve()
|
|
776
|
-
for artifact in
|
|
997
|
+
for artifact in artifacts:
|
|
777
998
|
candidate = (root / artifact).resolve()
|
|
778
999
|
if not _is_within_root(candidate, root):
|
|
779
1000
|
return False
|
|
780
1001
|
if glob.has_magic(str(candidate)):
|
|
781
1002
|
if not glob.glob(str(candidate)):
|
|
782
1003
|
return False
|
|
783
|
-
|
|
784
|
-
|
|
1004
|
+
else:
|
|
1005
|
+
try:
|
|
1006
|
+
if not candidate.exists():
|
|
1007
|
+
return False
|
|
1008
|
+
if candidate.is_file() and candidate.stat().st_size == 0:
|
|
1009
|
+
return False
|
|
1010
|
+
except OSError:
|
|
1011
|
+
return False
|
|
785
1012
|
return True
|
|
786
1013
|
|
|
787
1014
|
|
|
1015
|
+
def _valid_review_artifact(
|
|
1016
|
+
step: CheckpointStep,
|
|
1017
|
+
checkpoint_path: Path | None,
|
|
1018
|
+
project_root: Path | None,
|
|
1019
|
+
) -> bool:
|
|
1020
|
+
reports = [item for item in step.required_artifacts if item.endswith("/review-report.md")]
|
|
1021
|
+
if not reports:
|
|
1022
|
+
return _required_artifacts_exist(step, checkpoint_path, project_root)
|
|
1023
|
+
if len(reports) != 1 or not _required_artifacts_exist(step, checkpoint_path, project_root):
|
|
1024
|
+
return False
|
|
1025
|
+
root = (project_root or _infer_project_root(checkpoint_path)).resolve()
|
|
1026
|
+
report = (root / reports[0]).resolve()
|
|
1027
|
+
if not _is_within_root(report, root):
|
|
1028
|
+
return False
|
|
1029
|
+
try:
|
|
1030
|
+
text = report.read_text(encoding="utf-8")
|
|
1031
|
+
except (OSError, UnicodeError):
|
|
1032
|
+
return False
|
|
1033
|
+
final_sections = re.findall(
|
|
1034
|
+
r"(?ms)^## Final Result\s*$\n(?P<body>.*?)(?=^## |\Z)",
|
|
1035
|
+
text,
|
|
1036
|
+
)
|
|
1037
|
+
if len(final_sections) != 1:
|
|
1038
|
+
return False
|
|
1039
|
+
verdicts = re.findall(r"(?m)^- Verdict:\s*(PASS|NEEDS_FIXES)\s*$", final_sections[0])
|
|
1040
|
+
return verdicts == ["PASS"]
|
|
1041
|
+
|
|
1042
|
+
|
|
1043
|
+
def _valid_retrospective_artifact(
|
|
1044
|
+
step: CheckpointStep,
|
|
1045
|
+
checkpoint_path: Path | None,
|
|
1046
|
+
project_root: Path | None,
|
|
1047
|
+
) -> bool:
|
|
1048
|
+
results = [
|
|
1049
|
+
item for item in step.required_artifacts
|
|
1050
|
+
if item.endswith("/retrospective-result.json")
|
|
1051
|
+
]
|
|
1052
|
+
if not results:
|
|
1053
|
+
return _required_artifacts_exist(step, checkpoint_path, project_root)
|
|
1054
|
+
if len(results) != 1 or not _required_artifacts_exist(step, checkpoint_path, project_root):
|
|
1055
|
+
return False
|
|
1056
|
+
root = (project_root or _infer_project_root(checkpoint_path)).resolve()
|
|
1057
|
+
result_path = (root / results[0]).resolve()
|
|
1058
|
+
if not _is_within_root(result_path, root):
|
|
1059
|
+
return False
|
|
1060
|
+
try:
|
|
1061
|
+
value = json.loads(result_path.read_text(encoding="utf-8"))
|
|
1062
|
+
except (OSError, UnicodeError, json.JSONDecodeError):
|
|
1063
|
+
return False
|
|
1064
|
+
expected_keys = {
|
|
1065
|
+
"schema_version",
|
|
1066
|
+
"outcome",
|
|
1067
|
+
"result",
|
|
1068
|
+
"reason",
|
|
1069
|
+
"change_paths",
|
|
1070
|
+
"documentation_paths",
|
|
1071
|
+
"validation",
|
|
1072
|
+
}
|
|
1073
|
+
return bool(
|
|
1074
|
+
isinstance(value, dict)
|
|
1075
|
+
and set(value) == expected_keys
|
|
1076
|
+
and value.get("schema_version") == 1
|
|
1077
|
+
and value.get("outcome") == "RETRO_COMPLETE"
|
|
1078
|
+
and value.get("result") in {"DOCS_UPDATED", "NO_DOC_CHANGE"}
|
|
1079
|
+
and isinstance(value.get("reason"), str)
|
|
1080
|
+
and value["reason"].strip()
|
|
1081
|
+
and isinstance(value.get("change_paths"), list)
|
|
1082
|
+
and all(_valid_retrospective_change_path(path) for path in value["change_paths"])
|
|
1083
|
+
and isinstance(value.get("documentation_paths"), list)
|
|
1084
|
+
and all(
|
|
1085
|
+
_valid_retrospective_documentation_path(path)
|
|
1086
|
+
for path in value["documentation_paths"]
|
|
1087
|
+
)
|
|
1088
|
+
and isinstance(value.get("validation"), list)
|
|
1089
|
+
and all(isinstance(item, str) and item.strip() for item in value["validation"])
|
|
1090
|
+
and (
|
|
1091
|
+
value.get("result") != "DOCS_UPDATED"
|
|
1092
|
+
or (
|
|
1093
|
+
bool(value["documentation_paths"])
|
|
1094
|
+
and bool(value["validation"])
|
|
1095
|
+
)
|
|
1096
|
+
)
|
|
1097
|
+
)
|
|
1098
|
+
|
|
1099
|
+
|
|
1100
|
+
def _valid_retrospective_documentation_path(value: object) -> bool:
|
|
1101
|
+
if not isinstance(value, str) or not value.strip() or "\\" in value:
|
|
1102
|
+
return False
|
|
1103
|
+
raw_parts = value.split("/")
|
|
1104
|
+
if any(part in {"", ".", ".."} for part in raw_parts):
|
|
1105
|
+
return False
|
|
1106
|
+
path = PurePosixPath(value)
|
|
1107
|
+
return bool(
|
|
1108
|
+
not path.is_absolute()
|
|
1109
|
+
and len(raw_parts) >= 3
|
|
1110
|
+
and raw_parts[:2] == [".prizmkit", "prizm-docs"]
|
|
1111
|
+
and path.suffix == ".prizm"
|
|
1112
|
+
)
|
|
1113
|
+
|
|
1114
|
+
|
|
1115
|
+
def _valid_retrospective_change_path(value: object) -> bool:
|
|
1116
|
+
if not isinstance(value, str) or not value.strip() or "\\" in value:
|
|
1117
|
+
return False
|
|
1118
|
+
raw_parts = value.split("/")
|
|
1119
|
+
if any(part in {"", ".", ".."} for part in raw_parts):
|
|
1120
|
+
return False
|
|
1121
|
+
path = PurePosixPath(value)
|
|
1122
|
+
return bool(
|
|
1123
|
+
not path.is_absolute()
|
|
1124
|
+
and raw_parts[0] != ".prizmkit"
|
|
1125
|
+
)
|
|
1126
|
+
|
|
1127
|
+
|
|
788
1128
|
def _is_within_root(path: Path, root: Path) -> bool:
|
|
789
1129
|
try:
|
|
790
1130
|
path.relative_to(root)
|