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
|
@@ -10,12 +10,12 @@ Handles nine actions:
|
|
|
10
10
|
- reset: Reset a feature to pending (status + retry count)
|
|
11
11
|
- clean: Reset + delete session history + delete prizmkit artifacts
|
|
12
12
|
- complete: Shortcut for manually marking a feature as completed
|
|
13
|
-
-
|
|
13
|
+
- reset_state: Recover blocking roots and causally recoverable auto-skipped descendants
|
|
14
14
|
|
|
15
15
|
Usage:
|
|
16
16
|
python3 update-feature-status.py \
|
|
17
17
|
--feature-list <path> --state-dir <path> \
|
|
18
|
-
--action <get_next|start|update|status|pause|reset|clean|complete|
|
|
18
|
+
--action <get_next|start|update|status|pause|reset|clean|complete|reset_state> \
|
|
19
19
|
[--feature-id <id>] [--session-status <status>] \
|
|
20
20
|
[--session-id <id>] [--max-retries <n>] [--max-infra-retries <n>] \
|
|
21
21
|
[--features <filter>]
|
|
@@ -37,6 +37,7 @@ from utils import (
|
|
|
37
37
|
_build_progress_bar,
|
|
38
38
|
read_checkpoint_cursor,
|
|
39
39
|
dependency_rank_map,
|
|
40
|
+
causal_reset_selection,
|
|
40
41
|
)
|
|
41
42
|
|
|
42
43
|
|
|
@@ -58,6 +59,7 @@ SESSION_STATUS_VALUES = [
|
|
|
58
59
|
]
|
|
59
60
|
|
|
60
61
|
TERMINAL_STATUSES = {"completed", "failed", "skipped", "auto_skipped", "split"}
|
|
62
|
+
RESET_BLOCKING_ROOT_STATUSES = {"failed", "skipped"}
|
|
61
63
|
|
|
62
64
|
|
|
63
65
|
def parse_args():
|
|
@@ -77,7 +79,7 @@ def parse_args():
|
|
|
77
79
|
parser.add_argument(
|
|
78
80
|
"--action",
|
|
79
81
|
required=True,
|
|
80
|
-
choices=["get_next", "start", "update", "status", "pause", "reset", "clean", "complete", "
|
|
82
|
+
choices=["get_next", "start", "update", "status", "pause", "reset", "clean", "complete", "reset_state"],
|
|
81
83
|
help="Action to perform",
|
|
82
84
|
)
|
|
83
85
|
parser.add_argument(
|
|
@@ -909,6 +911,8 @@ def action_update(args, feature_list_path, state_dir):
|
|
|
909
911
|
|
|
910
912
|
fs = load_feature_status(state_dir, feature_id)
|
|
911
913
|
current_list_status = get_feature_status_from_list(feature_list_path, feature_id)
|
|
914
|
+
fs["max_retries"] = max_retries
|
|
915
|
+
fs["max_infra_retries"] = max_infra_retries
|
|
912
916
|
|
|
913
917
|
# Track what status we write to feature-list.json
|
|
914
918
|
new_status = current_list_status
|
|
@@ -1394,11 +1398,8 @@ def action_status(feature_list_data, state_dir, feature_filter=None, project_roo
|
|
|
1394
1398
|
fstatus = feature.get("status", "pending")
|
|
1395
1399
|
fs = load_feature_status(state_dir, fid)
|
|
1396
1400
|
feature_slug = _build_feature_slug(fid, title)
|
|
1397
|
-
# Read
|
|
1401
|
+
# Read the authoritative checkpoint cursor without mutating cached runtime state.
|
|
1398
1402
|
resume_phase = _checkpoint_phase_or_cached(project_root, feature_slug, fs)
|
|
1399
|
-
if resume_phase != fs.get("resume_from_phase"):
|
|
1400
|
-
fs["resume_from_phase"] = resume_phase
|
|
1401
|
-
save_feature_status(state_dir, fid, fs)
|
|
1402
1403
|
degraded_reason = degraded_reason_map.get(fid)
|
|
1403
1404
|
|
|
1404
1405
|
# Count statuses
|
|
@@ -1568,6 +1569,8 @@ def action_start(args, feature_list_path, state_dir):
|
|
|
1568
1569
|
fs = load_feature_status(state_dir, feature_id)
|
|
1569
1570
|
old_status = get_feature_status_from_list(feature_list_path, feature_id)
|
|
1570
1571
|
|
|
1572
|
+
fs["max_retries"] = args.max_retries
|
|
1573
|
+
fs["max_infra_retries"] = args.max_infra_retries
|
|
1571
1574
|
fs["updated_at"] = now_iso()
|
|
1572
1575
|
|
|
1573
1576
|
err = save_feature_status(state_dir, feature_id, fs)
|
|
@@ -1749,124 +1752,37 @@ def action_clean(args, feature_list_path, state_dir):
|
|
|
1749
1752
|
|
|
1750
1753
|
|
|
1751
1754
|
# ---------------------------------------------------------------------------
|
|
1752
|
-
# Action:
|
|
1755
|
+
# Action: reset_state
|
|
1753
1756
|
# ---------------------------------------------------------------------------
|
|
1754
1757
|
|
|
1755
|
-
def
|
|
1756
|
-
"""
|
|
1757
|
-
|
|
1758
|
-
Two modes:
|
|
1759
|
-
- --feature-id F-032: Reset the specified failed/skipped feature + all auto_skipped
|
|
1760
|
-
features whose dependency chain includes it.
|
|
1761
|
-
- No --feature-id: Reset ALL failed, skipped, and auto_skipped features to pending.
|
|
1762
|
-
"""
|
|
1763
|
-
feature_id = args.feature_id
|
|
1764
|
-
|
|
1758
|
+
def action_reset_state(args, feature_list_path, state_dir):
|
|
1759
|
+
"""Reset one causal feature closure, or every recoverable closure, to pending."""
|
|
1765
1760
|
data, err = load_json_file(feature_list_path)
|
|
1766
1761
|
if err:
|
|
1767
1762
|
error_out("Cannot load feature list: {}".format(err))
|
|
1768
1763
|
return
|
|
1769
1764
|
features = data.get("features", [])
|
|
1770
1765
|
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
if not target:
|
|
1781
|
-
error_out("Feature '{}' not found in .prizmkit/plans/feature-list.json".format(feature_id))
|
|
1782
|
-
return
|
|
1783
|
-
if target.get("status") not in ("failed", "skipped", "auto_skipped"):
|
|
1784
|
-
error_out(
|
|
1785
|
-
"Feature '{}' has status '{}', expected 'failed', 'skipped', or 'auto_skipped'".format(
|
|
1786
|
-
feature_id, target.get("status", "unknown")
|
|
1787
|
-
)
|
|
1788
|
-
)
|
|
1789
|
-
return
|
|
1790
|
-
|
|
1791
|
-
# If target is failed or skipped, reset it and find all auto_skipped descendants
|
|
1792
|
-
if target.get("status") in ("failed", "skipped"):
|
|
1793
|
-
to_reset.add(feature_id)
|
|
1794
|
-
# Find all auto_skipped features that depend (transitively) on this one
|
|
1795
|
-
changed = True
|
|
1796
|
-
while changed:
|
|
1797
|
-
changed = False
|
|
1798
|
-
for f in features:
|
|
1799
|
-
if not isinstance(f, dict):
|
|
1800
|
-
continue
|
|
1801
|
-
fid = f.get("id")
|
|
1802
|
-
if not fid or fid in to_reset:
|
|
1803
|
-
continue
|
|
1804
|
-
if f.get("status") != "auto_skipped":
|
|
1805
|
-
continue
|
|
1806
|
-
deps = f.get("dependencies", [])
|
|
1807
|
-
if any(d in to_reset for d in deps):
|
|
1808
|
-
to_reset.add(fid)
|
|
1809
|
-
changed = True
|
|
1810
|
-
|
|
1811
|
-
# If target is auto_skipped, reset it and its failed upstream + siblings
|
|
1812
|
-
elif target.get("status") == "auto_skipped":
|
|
1813
|
-
to_reset.add(feature_id)
|
|
1814
|
-
# Transitively walk upstream to find ALL failed/auto_skipped ancestors
|
|
1815
|
-
# (e.g., F-001 failed → F-002 auto_skipped → F-003 auto_skipped;
|
|
1816
|
-
# unskip F-003 must also find and reset F-001)
|
|
1817
|
-
upstream_changed = True
|
|
1818
|
-
while upstream_changed:
|
|
1819
|
-
upstream_changed = False
|
|
1820
|
-
for f in features:
|
|
1821
|
-
if not isinstance(f, dict):
|
|
1822
|
-
continue
|
|
1823
|
-
fid = f.get("id")
|
|
1824
|
-
if not fid or fid not in to_reset:
|
|
1825
|
-
continue
|
|
1826
|
-
for dep_id in f.get("dependencies", []):
|
|
1827
|
-
if dep_id in to_reset:
|
|
1828
|
-
continue
|
|
1829
|
-
for dep_f in features:
|
|
1830
|
-
if isinstance(dep_f, dict) and dep_f.get("id") == dep_id:
|
|
1831
|
-
if dep_f.get("status") in ("failed", "skipped", "auto_skipped"):
|
|
1832
|
-
to_reset.add(dep_id)
|
|
1833
|
-
upstream_changed = True
|
|
1834
|
-
# Also reset downstream auto_skipped features blocked by the same upstreams
|
|
1835
|
-
changed = True
|
|
1836
|
-
while changed:
|
|
1837
|
-
changed = False
|
|
1838
|
-
for f in features:
|
|
1839
|
-
if not isinstance(f, dict):
|
|
1840
|
-
continue
|
|
1841
|
-
fid = f.get("id")
|
|
1842
|
-
if not fid or fid in to_reset:
|
|
1843
|
-
continue
|
|
1844
|
-
if f.get("status") != "auto_skipped":
|
|
1845
|
-
continue
|
|
1846
|
-
fdeps = f.get("dependencies", [])
|
|
1847
|
-
if any(d in to_reset for d in fdeps):
|
|
1848
|
-
to_reset.add(fid)
|
|
1849
|
-
changed = True
|
|
1850
|
-
else:
|
|
1851
|
-
# No feature-id: reset ALL failed + skipped + auto_skipped
|
|
1852
|
-
for f in features:
|
|
1853
|
-
if isinstance(f, dict) and f.get("id"):
|
|
1854
|
-
if f.get("status") in ("failed", "skipped", "auto_skipped"):
|
|
1855
|
-
to_reset.add(f["id"])
|
|
1856
|
-
|
|
1857
|
-
if not to_reset:
|
|
1858
|
-
error_out("No features to unskip")
|
|
1766
|
+
try:
|
|
1767
|
+
recovery_ids = causal_reset_selection(
|
|
1768
|
+
features,
|
|
1769
|
+
selected_id=args.feature_id,
|
|
1770
|
+
blocking_root_statuses=RESET_BLOCKING_ROOT_STATUSES,
|
|
1771
|
+
item_label="Feature",
|
|
1772
|
+
)
|
|
1773
|
+
except ValueError as exc:
|
|
1774
|
+
error_out(str(exc))
|
|
1859
1775
|
return
|
|
1860
1776
|
|
|
1861
|
-
|
|
1777
|
+
to_reset = set(recovery_ids)
|
|
1862
1778
|
reset_details = []
|
|
1863
|
-
for
|
|
1864
|
-
if isinstance(
|
|
1865
|
-
old_status =
|
|
1866
|
-
|
|
1779
|
+
for feature in features:
|
|
1780
|
+
if isinstance(feature, dict) and feature.get("id") in to_reset:
|
|
1781
|
+
old_status = feature.get("status", "unknown")
|
|
1782
|
+
feature["status"] = "pending"
|
|
1867
1783
|
reset_details.append({
|
|
1868
|
-
"feature_id":
|
|
1869
|
-
"title":
|
|
1784
|
+
"feature_id": feature["id"],
|
|
1785
|
+
"title": feature.get("title", ""),
|
|
1870
1786
|
"old_status": old_status,
|
|
1871
1787
|
})
|
|
1872
1788
|
|
|
@@ -1875,22 +1791,31 @@ def action_unskip(args, feature_list_path, state_dir):
|
|
|
1875
1791
|
error_out("Failed to write .prizmkit/plans/feature-list.json: {}".format(err))
|
|
1876
1792
|
return
|
|
1877
1793
|
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1794
|
+
for feature_id in recovery_ids:
|
|
1795
|
+
fs = load_feature_status(state_dir, feature_id)
|
|
1796
|
+
checkout_identity = {
|
|
1797
|
+
key: fs[key]
|
|
1798
|
+
for key in ("active_dev_branch", "base_branch")
|
|
1799
|
+
if key in fs
|
|
1800
|
+
}
|
|
1881
1801
|
fs["retry_count"] = 0
|
|
1882
1802
|
fs["infra_error_count"] = 0
|
|
1883
1803
|
fs["last_infra_error_session_id"] = None
|
|
1884
1804
|
fs["sessions"] = []
|
|
1885
1805
|
fs["last_session_id"] = None
|
|
1806
|
+
fs["last_failed_session_id"] = None
|
|
1886
1807
|
fs["resume_from_phase"] = None
|
|
1887
1808
|
reset_continuation_metadata(fs)
|
|
1809
|
+
fs.update(checkout_identity)
|
|
1888
1810
|
fs["updated_at"] = now_iso()
|
|
1889
|
-
save_feature_status(state_dir,
|
|
1811
|
+
err = save_feature_status(state_dir, feature_id, fs)
|
|
1812
|
+
if err:
|
|
1813
|
+
error_out("Failed to save feature status: {}".format(err))
|
|
1814
|
+
return
|
|
1890
1815
|
|
|
1891
1816
|
result = {
|
|
1892
|
-
"action": "
|
|
1893
|
-
"reset_count": len(
|
|
1817
|
+
"action": "reset_state",
|
|
1818
|
+
"reset_count": len(recovery_ids),
|
|
1894
1819
|
"features": reset_details,
|
|
1895
1820
|
}
|
|
1896
1821
|
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
@@ -1977,8 +1902,8 @@ def main():
|
|
|
1977
1902
|
action_update(args, args.feature_list, args.state_dir)
|
|
1978
1903
|
elif args.action == "pause":
|
|
1979
1904
|
action_pause(args.state_dir)
|
|
1980
|
-
elif args.action == "
|
|
1981
|
-
|
|
1905
|
+
elif args.action == "reset_state":
|
|
1906
|
+
action_reset_state(args, args.feature_list, args.state_dir)
|
|
1982
1907
|
|
|
1983
1908
|
|
|
1984
1909
|
if __name__ == "__main__":
|
|
@@ -9,12 +9,12 @@ Handles eight actions:
|
|
|
9
9
|
- pause: Save pipeline state for graceful shutdown
|
|
10
10
|
- reset: Reset a refactor to pending (status + retry count)
|
|
11
11
|
- clean: Reset + delete session history + delete refactor artifacts
|
|
12
|
-
-
|
|
12
|
+
- reset_state: Recover blocking roots and causally recoverable auto-skipped descendants
|
|
13
13
|
|
|
14
14
|
Usage:
|
|
15
15
|
python3 update-refactor-status.py \
|
|
16
16
|
--refactor-list <path> --state-dir <path> \
|
|
17
|
-
--action <get_next|start|update|status|pause|reset|clean|
|
|
17
|
+
--action <get_next|start|update|status|pause|reset|clean|reset_state> \
|
|
18
18
|
[--refactor-id <id>] [--session-status <status>] \
|
|
19
19
|
[--session-id <id>] [--max-retries <n>] [--max-infra-retries <n>]
|
|
20
20
|
"""
|
|
@@ -34,6 +34,7 @@ from utils import (
|
|
|
34
34
|
_build_progress_bar,
|
|
35
35
|
read_checkpoint_cursor,
|
|
36
36
|
dependency_rank_map,
|
|
37
|
+
causal_reset_selection,
|
|
37
38
|
)
|
|
38
39
|
|
|
39
40
|
|
|
@@ -55,6 +56,7 @@ SESSION_STATUS_VALUES = [
|
|
|
55
56
|
]
|
|
56
57
|
|
|
57
58
|
TERMINAL_STATUSES = {"completed", "failed", "skipped", "auto_skipped"}
|
|
59
|
+
RESET_BLOCKING_ROOT_STATUSES = {"failed", "skipped"}
|
|
58
60
|
|
|
59
61
|
# Artifact directory names (relative to project root)
|
|
60
62
|
REFACTOR_ARTIFACTS_REL = os.path.join(".prizmkit", "refactor")
|
|
@@ -84,7 +86,7 @@ def parse_args():
|
|
|
84
86
|
parser.add_argument("--state-dir", required=True, help="Path to the state directory (default: .prizmkit/state/refactor)")
|
|
85
87
|
parser.add_argument(
|
|
86
88
|
"--action", required=True,
|
|
87
|
-
choices=["get_next", "start", "update", "status", "pause", "reset", "clean", "
|
|
89
|
+
choices=["get_next", "start", "update", "status", "pause", "reset", "clean", "reset_state", "complete"],
|
|
88
90
|
help="Action to perform",
|
|
89
91
|
)
|
|
90
92
|
parser.add_argument("--refactor-id", default=None, help="Refactor ID (required for 'update'/'reset'/'clean' actions)")
|
|
@@ -517,6 +519,8 @@ def action_update(args, refactor_list_path, state_dir):
|
|
|
517
519
|
rs = load_refactor_status(state_dir, refactor_id)
|
|
518
520
|
|
|
519
521
|
current_list_status = get_refactor_status_from_list(refactor_list_path, refactor_id)
|
|
522
|
+
rs["max_retries"] = max_retries
|
|
523
|
+
rs["max_infra_retries"] = max_infra_retries
|
|
520
524
|
|
|
521
525
|
# Track what status we write to refactor-list.json
|
|
522
526
|
new_status = current_list_status
|
|
@@ -1006,11 +1010,8 @@ def action_status(refactor_list_data, state_dir, project_root=None):
|
|
|
1006
1010
|
rs = load_refactor_status(state_dir, rid)
|
|
1007
1011
|
retry_count = rs.get("retry_count", 0)
|
|
1008
1012
|
max_retries_val = rs.get("max_retries", 3)
|
|
1009
|
-
# Read
|
|
1013
|
+
# Read the authoritative checkpoint cursor without mutating cached runtime state.
|
|
1010
1014
|
resume_phase = _checkpoint_phase_or_cached(project_root, rid, rs)
|
|
1011
|
-
if resume_phase != rs.get("resume_from_phase"):
|
|
1012
|
-
rs["resume_from_phase"] = resume_phase
|
|
1013
|
-
save_refactor_status(state_dir, rid, rs)
|
|
1014
1015
|
|
|
1015
1016
|
if rstatus in counts:
|
|
1016
1017
|
counts[rstatus] += 1
|
|
@@ -1263,6 +1264,8 @@ def action_start(args, refactor_list_path, state_dir):
|
|
|
1263
1264
|
rs = load_refactor_status(state_dir, refactor_id)
|
|
1264
1265
|
old_status = get_refactor_status_from_list(refactor_list_path, refactor_id)
|
|
1265
1266
|
|
|
1267
|
+
rs["max_retries"] = args.max_retries
|
|
1268
|
+
rs["max_infra_retries"] = args.max_infra_retries
|
|
1266
1269
|
rs["updated_at"] = now_iso()
|
|
1267
1270
|
|
|
1268
1271
|
err = save_refactor_status(state_dir, refactor_id, rs)
|
|
@@ -1289,126 +1292,37 @@ def action_start(args, refactor_list_path, state_dir):
|
|
|
1289
1292
|
|
|
1290
1293
|
|
|
1291
1294
|
# ---------------------------------------------------------------------------
|
|
1292
|
-
# Action:
|
|
1295
|
+
# Action: reset_state
|
|
1293
1296
|
# ---------------------------------------------------------------------------
|
|
1294
1297
|
|
|
1295
|
-
def
|
|
1296
|
-
"""
|
|
1297
|
-
|
|
1298
|
-
Two modes:
|
|
1299
|
-
- --refactor-id R-001: Reset the specified failed/skipped/auto_skipped refactor + all
|
|
1300
|
-
downstream auto_skipped refactors whose dependency chain includes it.
|
|
1301
|
-
If the target is auto_skipped, also walk upstream to find and reset the
|
|
1302
|
-
failed/skipped ancestor that caused the cascade.
|
|
1303
|
-
- No --refactor-id: Reset ALL failed, skipped, and auto_skipped refactors to pending.
|
|
1304
|
-
"""
|
|
1305
|
-
refactor_id = args.refactor_id
|
|
1306
|
-
|
|
1298
|
+
def action_reset_state(args, refactor_list_path, state_dir):
|
|
1299
|
+
"""Reset one causal refactor closure, or every recoverable closure, to pending."""
|
|
1307
1300
|
data, err = load_json_file(refactor_list_path)
|
|
1308
1301
|
if err:
|
|
1309
1302
|
error_out("Cannot load refactor list: {}".format(err))
|
|
1310
1303
|
return
|
|
1311
1304
|
refactors = data.get("refactors", [])
|
|
1312
1305
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
if not target:
|
|
1323
|
-
error_out("Refactor '{}' not found in .prizmkit/plans/refactor-list.json".format(refactor_id))
|
|
1324
|
-
return
|
|
1325
|
-
if target.get("status") not in ("failed", "skipped", "auto_skipped"):
|
|
1326
|
-
error_out(
|
|
1327
|
-
"Refactor '{}' has status '{}', expected 'failed', 'skipped', or 'auto_skipped'".format(
|
|
1328
|
-
refactor_id, target.get("status", "unknown")
|
|
1329
|
-
)
|
|
1330
|
-
)
|
|
1331
|
-
return
|
|
1332
|
-
|
|
1333
|
-
# If target is failed or skipped, reset it and find all auto_skipped descendants
|
|
1334
|
-
if target.get("status") in ("failed", "skipped"):
|
|
1335
|
-
to_reset.add(refactor_id)
|
|
1336
|
-
# Find all auto_skipped refactors that depend (transitively) on this one
|
|
1337
|
-
changed = True
|
|
1338
|
-
while changed:
|
|
1339
|
-
changed = False
|
|
1340
|
-
for r in refactors:
|
|
1341
|
-
if not isinstance(r, dict):
|
|
1342
|
-
continue
|
|
1343
|
-
rid = r.get("id")
|
|
1344
|
-
if not rid or rid in to_reset:
|
|
1345
|
-
continue
|
|
1346
|
-
if r.get("status") != "auto_skipped":
|
|
1347
|
-
continue
|
|
1348
|
-
deps = r.get("dependencies", [])
|
|
1349
|
-
if any(d in to_reset for d in deps):
|
|
1350
|
-
to_reset.add(rid)
|
|
1351
|
-
changed = True
|
|
1352
|
-
|
|
1353
|
-
# If target is auto_skipped, reset it and its failed upstream + siblings
|
|
1354
|
-
elif target.get("status") == "auto_skipped":
|
|
1355
|
-
to_reset.add(refactor_id)
|
|
1356
|
-
# Transitively walk upstream to find ALL failed/auto_skipped ancestors
|
|
1357
|
-
# (e.g., R-001 failed → R-002 auto_skipped → R-003 auto_skipped;
|
|
1358
|
-
# unskip R-003 must also find and reset R-001)
|
|
1359
|
-
upstream_changed = True
|
|
1360
|
-
while upstream_changed:
|
|
1361
|
-
upstream_changed = False
|
|
1362
|
-
for r in refactors:
|
|
1363
|
-
if not isinstance(r, dict):
|
|
1364
|
-
continue
|
|
1365
|
-
rid = r.get("id")
|
|
1366
|
-
if not rid or rid not in to_reset:
|
|
1367
|
-
continue
|
|
1368
|
-
for dep_id in r.get("dependencies", []):
|
|
1369
|
-
if dep_id in to_reset:
|
|
1370
|
-
continue
|
|
1371
|
-
for dep_r in refactors:
|
|
1372
|
-
if isinstance(dep_r, dict) and dep_r.get("id") == dep_id:
|
|
1373
|
-
if dep_r.get("status") in ("failed", "skipped", "auto_skipped"):
|
|
1374
|
-
to_reset.add(dep_id)
|
|
1375
|
-
upstream_changed = True
|
|
1376
|
-
# Also reset downstream auto_skipped refactors blocked by the same upstreams
|
|
1377
|
-
changed = True
|
|
1378
|
-
while changed:
|
|
1379
|
-
changed = False
|
|
1380
|
-
for r in refactors:
|
|
1381
|
-
if not isinstance(r, dict):
|
|
1382
|
-
continue
|
|
1383
|
-
rid = r.get("id")
|
|
1384
|
-
if not rid or rid in to_reset:
|
|
1385
|
-
continue
|
|
1386
|
-
if r.get("status") != "auto_skipped":
|
|
1387
|
-
continue
|
|
1388
|
-
rdeps = r.get("dependencies", [])
|
|
1389
|
-
if any(d in to_reset for d in rdeps):
|
|
1390
|
-
to_reset.add(rid)
|
|
1391
|
-
changed = True
|
|
1392
|
-
else:
|
|
1393
|
-
# No refactor-id: reset ALL failed + skipped + auto_skipped
|
|
1394
|
-
for r in refactors:
|
|
1395
|
-
if isinstance(r, dict) and r.get("id"):
|
|
1396
|
-
if r.get("status") in ("failed", "skipped", "auto_skipped"):
|
|
1397
|
-
to_reset.add(r["id"])
|
|
1398
|
-
|
|
1399
|
-
if not to_reset:
|
|
1400
|
-
error_out("No refactors to unskip")
|
|
1306
|
+
try:
|
|
1307
|
+
recovery_ids = causal_reset_selection(
|
|
1308
|
+
refactors,
|
|
1309
|
+
selected_id=args.refactor_id,
|
|
1310
|
+
blocking_root_statuses=RESET_BLOCKING_ROOT_STATUSES,
|
|
1311
|
+
item_label="Refactor",
|
|
1312
|
+
)
|
|
1313
|
+
except ValueError as exc:
|
|
1314
|
+
error_out(str(exc))
|
|
1401
1315
|
return
|
|
1402
1316
|
|
|
1403
|
-
|
|
1317
|
+
to_reset = set(recovery_ids)
|
|
1404
1318
|
reset_details = []
|
|
1405
|
-
for
|
|
1406
|
-
if isinstance(
|
|
1407
|
-
old_status =
|
|
1408
|
-
|
|
1319
|
+
for refactor in refactors:
|
|
1320
|
+
if isinstance(refactor, dict) and refactor.get("id") in to_reset:
|
|
1321
|
+
old_status = refactor.get("status", "unknown")
|
|
1322
|
+
refactor["status"] = "pending"
|
|
1409
1323
|
reset_details.append({
|
|
1410
|
-
"refactor_id":
|
|
1411
|
-
"title":
|
|
1324
|
+
"refactor_id": refactor["id"],
|
|
1325
|
+
"title": refactor.get("title", ""),
|
|
1412
1326
|
"old_status": old_status,
|
|
1413
1327
|
})
|
|
1414
1328
|
|
|
@@ -1417,22 +1331,31 @@ def action_unskip(args, refactor_list_path, state_dir):
|
|
|
1417
1331
|
error_out("Failed to write .prizmkit/plans/refactor-list.json: {}".format(err))
|
|
1418
1332
|
return
|
|
1419
1333
|
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1334
|
+
for refactor_id in recovery_ids:
|
|
1335
|
+
rs = load_refactor_status(state_dir, refactor_id)
|
|
1336
|
+
checkout_identity = {
|
|
1337
|
+
key: rs[key]
|
|
1338
|
+
for key in ("active_dev_branch", "base_branch")
|
|
1339
|
+
if key in rs
|
|
1340
|
+
}
|
|
1423
1341
|
rs["retry_count"] = 0
|
|
1424
1342
|
rs["infra_error_count"] = 0
|
|
1425
1343
|
rs["last_infra_error_session_id"] = None
|
|
1426
1344
|
rs["sessions"] = []
|
|
1427
1345
|
rs["last_session_id"] = None
|
|
1346
|
+
rs["last_failed_session_id"] = None
|
|
1428
1347
|
rs["resume_from_phase"] = None
|
|
1429
1348
|
reset_continuation_metadata(rs)
|
|
1349
|
+
rs.update(checkout_identity)
|
|
1430
1350
|
rs["updated_at"] = now_iso()
|
|
1431
|
-
save_refactor_status(state_dir,
|
|
1351
|
+
err = save_refactor_status(state_dir, refactor_id, rs)
|
|
1352
|
+
if err:
|
|
1353
|
+
error_out("Failed to save refactor status: {}".format(err))
|
|
1354
|
+
return
|
|
1432
1355
|
|
|
1433
1356
|
result = {
|
|
1434
|
-
"action": "
|
|
1435
|
-
"reset_count": len(
|
|
1357
|
+
"action": "reset_state",
|
|
1358
|
+
"reset_count": len(recovery_ids),
|
|
1436
1359
|
"refactors": reset_details,
|
|
1437
1360
|
}
|
|
1438
1361
|
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
@@ -1475,8 +1398,8 @@ def main():
|
|
|
1475
1398
|
action_start(args, args.refactor_list, args.state_dir)
|
|
1476
1399
|
elif args.action == "pause":
|
|
1477
1400
|
action_pause(args.state_dir)
|
|
1478
|
-
elif args.action == "
|
|
1479
|
-
|
|
1401
|
+
elif args.action == "reset_state":
|
|
1402
|
+
action_reset_state(args, args.refactor_list, args.state_dir)
|
|
1480
1403
|
elif args.action == "complete":
|
|
1481
1404
|
# Shortcut: 'complete' is equivalent to 'update --session-status success'
|
|
1482
1405
|
args.session_status = "success"
|
|
@@ -228,6 +228,125 @@ def dependency_rank_map(items, id_key="id", dependencies_key="dependencies", ite
|
|
|
228
228
|
return {item[id_key]: index for index, item in enumerate(ordered) if id_key in item}, errors
|
|
229
229
|
|
|
230
230
|
|
|
231
|
+
def causal_reset_selection(
|
|
232
|
+
items,
|
|
233
|
+
selected_id=None,
|
|
234
|
+
blocking_root_statuses=None,
|
|
235
|
+
id_key="id",
|
|
236
|
+
dependencies_key="dependencies",
|
|
237
|
+
status_key="status",
|
|
238
|
+
auto_skipped_status="auto_skipped",
|
|
239
|
+
item_label="Item",
|
|
240
|
+
):
|
|
241
|
+
"""Return the exact state-only recovery closure for one dependency graph.
|
|
242
|
+
|
|
243
|
+
A selected terminal root recovers itself and only ``auto_skipped`` items
|
|
244
|
+
whose complete transitive blocking-root set is contained by that selection.
|
|
245
|
+
Selecting an ``auto_skipped`` item first expands the selection to every
|
|
246
|
+
blocking root required by that item. ``selected_id=None`` means recover all
|
|
247
|
+
supported blocking roots. Invalid graph or target state raises ``ValueError``
|
|
248
|
+
before callers perform any mutation.
|
|
249
|
+
"""
|
|
250
|
+
if not isinstance(items, list):
|
|
251
|
+
raise ValueError("{} list must be an array".format(item_label))
|
|
252
|
+
|
|
253
|
+
roots_statuses = set(blocking_root_statuses or ())
|
|
254
|
+
if not roots_statuses:
|
|
255
|
+
raise ValueError("Blocking root statuses must not be empty")
|
|
256
|
+
|
|
257
|
+
item_by_id = {}
|
|
258
|
+
ordered_ids = []
|
|
259
|
+
for index, item in enumerate(items):
|
|
260
|
+
if not isinstance(item, dict):
|
|
261
|
+
raise ValueError("{} at index {} is not an object".format(item_label, index))
|
|
262
|
+
item_id = item.get(id_key)
|
|
263
|
+
if not isinstance(item_id, str) or not item_id:
|
|
264
|
+
raise ValueError("{} at index {} has no valid id".format(item_label, index))
|
|
265
|
+
if item_id in item_by_id:
|
|
266
|
+
raise ValueError("Duplicate {} id: {}".format(item_label.lower(), item_id))
|
|
267
|
+
item_by_id[item_id] = item
|
|
268
|
+
ordered_ids.append(item_id)
|
|
269
|
+
|
|
270
|
+
_ordered, graph_errors = stable_topological_order(
|
|
271
|
+
items,
|
|
272
|
+
id_key=id_key,
|
|
273
|
+
dependencies_key=dependencies_key,
|
|
274
|
+
item_label=item_label,
|
|
275
|
+
)
|
|
276
|
+
if graph_errors:
|
|
277
|
+
raise ValueError(graph_errors[0])
|
|
278
|
+
|
|
279
|
+
blocker_cache = {}
|
|
280
|
+
|
|
281
|
+
def transitive_blockers(item_id):
|
|
282
|
+
cached = blocker_cache.get(item_id)
|
|
283
|
+
if cached is not None:
|
|
284
|
+
cached_roots, cached_unresolved = cached
|
|
285
|
+
return set(cached_roots), set(cached_unresolved)
|
|
286
|
+
item = item_by_id[item_id]
|
|
287
|
+
roots = set()
|
|
288
|
+
unresolved = set()
|
|
289
|
+
item_status = item.get(status_key, "pending")
|
|
290
|
+
if item_status in roots_statuses:
|
|
291
|
+
roots.add(item_id)
|
|
292
|
+
for dependency_id in item.get(dependencies_key, []) or []:
|
|
293
|
+
dependency_roots, dependency_unresolved = transitive_blockers(dependency_id)
|
|
294
|
+
roots.update(dependency_roots)
|
|
295
|
+
unresolved.update(dependency_unresolved)
|
|
296
|
+
if item_status == auto_skipped_status and not roots and not unresolved:
|
|
297
|
+
unresolved.add(item_id)
|
|
298
|
+
blocker_cache[item_id] = (frozenset(roots), frozenset(unresolved))
|
|
299
|
+
return roots, unresolved
|
|
300
|
+
|
|
301
|
+
if selected_id is None:
|
|
302
|
+
selected_roots = {
|
|
303
|
+
item_id
|
|
304
|
+
for item_id in ordered_ids
|
|
305
|
+
if item_by_id[item_id].get(status_key, "pending") in roots_statuses
|
|
306
|
+
}
|
|
307
|
+
if not selected_roots:
|
|
308
|
+
raise ValueError("No recoverable blocking roots found")
|
|
309
|
+
else:
|
|
310
|
+
target = item_by_id.get(selected_id)
|
|
311
|
+
if target is None:
|
|
312
|
+
raise ValueError("{} '{}' not found".format(item_label, selected_id))
|
|
313
|
+
target_status = target.get(status_key, "pending")
|
|
314
|
+
if target_status in roots_statuses:
|
|
315
|
+
selected_roots = {selected_id}
|
|
316
|
+
elif target_status == auto_skipped_status:
|
|
317
|
+
selected_roots, unresolved = transitive_blockers(selected_id)
|
|
318
|
+
if unresolved:
|
|
319
|
+
raise ValueError(
|
|
320
|
+
"{} '{}' has unresolved auto_skipped blocker(s): {}".format(
|
|
321
|
+
item_label, selected_id, ", ".join(sorted(unresolved))
|
|
322
|
+
)
|
|
323
|
+
)
|
|
324
|
+
if not selected_roots:
|
|
325
|
+
raise ValueError(
|
|
326
|
+
"{} '{}' is auto_skipped but has no transitive blocking root".format(
|
|
327
|
+
item_label, selected_id
|
|
328
|
+
)
|
|
329
|
+
)
|
|
330
|
+
else:
|
|
331
|
+
supported = sorted(roots_statuses | {auto_skipped_status})
|
|
332
|
+
raise ValueError(
|
|
333
|
+
"{} '{}' has status '{}', which is not recoverable; expected one of {}".format(
|
|
334
|
+
item_label, selected_id, target_status, ", ".join(supported)
|
|
335
|
+
)
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
selected = set(selected_roots)
|
|
339
|
+
for item_id in ordered_ids:
|
|
340
|
+
item = item_by_id[item_id]
|
|
341
|
+
if item.get(status_key, "pending") != auto_skipped_status:
|
|
342
|
+
continue
|
|
343
|
+
blockers, unresolved = transitive_blockers(item_id)
|
|
344
|
+
if blockers and not unresolved and blockers.issubset(selected_roots):
|
|
345
|
+
selected.add(item_id)
|
|
346
|
+
|
|
347
|
+
return tuple(item_id for item_id in ordered_ids if item_id in selected)
|
|
348
|
+
|
|
349
|
+
|
|
231
350
|
def setup_logging(name="prizmkit.dev_pipeline", level=None):
|
|
232
351
|
"""Configure and return a standard logger for pipeline scripts.
|
|
233
352
|
|