prizmkit 1.1.7 → 1.1.9
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/codebuddy/skill-adapter.js +21 -7
- package/bundled/agents/prizm-dev-team-reviewer.md +53 -173
- package/bundled/dev-pipeline/.env.example +45 -0
- package/bundled/dev-pipeline/README.md +64 -64
- package/bundled/dev-pipeline/SCHEMA_ANALYSIS.md +535 -0
- package/bundled/dev-pipeline/assets/feature-list-example.json +0 -1
- package/bundled/dev-pipeline/launch-bugfix-daemon.sh +64 -18
- package/bundled/dev-pipeline/launch-feature-daemon.sh +15 -12
- package/bundled/dev-pipeline/launch-refactor-daemon.sh +64 -18
- package/bundled/dev-pipeline/lib/branch.sh +6 -1
- package/bundled/dev-pipeline/lib/common.sh +71 -0
- package/bundled/dev-pipeline/lib/heartbeat.sh +2 -2
- package/bundled/dev-pipeline/reset-bug.sh +10 -9
- package/bundled/dev-pipeline/reset-feature.sh +9 -8
- package/bundled/dev-pipeline/reset-refactor.sh +10 -9
- package/bundled/dev-pipeline/retry-bugfix.sh +67 -29
- package/bundled/dev-pipeline/retry-feature.sh +54 -18
- package/bundled/dev-pipeline/retry-refactor.sh +112 -29
- package/bundled/dev-pipeline/run-bugfix.sh +281 -59
- package/bundled/dev-pipeline/run-feature.sh +53 -18
- package/bundled/dev-pipeline/run-refactor.sh +392 -66
- package/bundled/dev-pipeline/scripts/check-session-status.py +24 -1
- package/bundled/dev-pipeline/scripts/cleanup-logs.py +2 -2
- package/bundled/dev-pipeline/scripts/detect-stuck.py +195 -85
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +57 -33
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +25 -9
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +104 -17
- package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +34 -9
- package/bundled/dev-pipeline/scripts/init-pipeline.py +10 -10
- package/bundled/dev-pipeline/scripts/init-refactor-pipeline.py +19 -8
- package/bundled/dev-pipeline/scripts/parse-stream-progress.py +1 -5
- package/bundled/dev-pipeline/scripts/patch-completion-notes.py +191 -0
- package/bundled/dev-pipeline/scripts/update-bug-status.py +167 -22
- package/bundled/dev-pipeline/scripts/update-feature-status.py +104 -62
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +351 -21
- package/bundled/dev-pipeline/templates/agent-prompts/dev-fix.md +1 -1
- package/bundled/dev-pipeline/templates/agent-prompts/reviewer-review.md +7 -11
- package/bundled/dev-pipeline/templates/bootstrap-prompt.md +41 -7
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +27 -3
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +43 -19
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +54 -26
- package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +6 -15
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +36 -25
- package/bundled/dev-pipeline/templates/feature-list-schema.json +109 -31
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +270 -0
- package/bundled/dev-pipeline/templates/refactor-list-schema.json +11 -3
- package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +3 -1
- package/bundled/dev-pipeline/templates/sections/critical-paths-agent.md +1 -0
- package/bundled/dev-pipeline/templates/sections/feature-context.md +2 -0
- package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +29 -2
- package/bundled/dev-pipeline/templates/sections/phase-commit.md +22 -0
- package/bundled/dev-pipeline/templates/sections/phase-deploy-verification.md +2 -2
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +8 -6
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +7 -5
- package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +3 -3
- package/bundled/skills/_metadata.json +5 -22
- package/bundled/skills/app-planner/SKILL.md +98 -72
- package/bundled/skills/app-planner/assets/app-design-guide.md +1 -1
- package/bundled/skills/app-planner/references/architecture-decisions.md +1 -1
- package/bundled/skills/app-planner/references/project-brief-guide.md +69 -66
- package/bundled/skills/bug-fix-workflow/SKILL.md +52 -9
- package/bundled/skills/bug-planner/SKILL.md +139 -197
- package/bundled/skills/bug-planner/assets/bug-confirmation-template.md +43 -0
- package/bundled/skills/bug-planner/references/critic-and-verification.md +44 -0
- package/bundled/skills/bug-planner/references/error-recovery.md +73 -0
- package/bundled/skills/bug-planner/references/input-formats.md +53 -0
- package/bundled/skills/bug-planner/references/schema-validation.md +25 -0
- package/bundled/skills/bug-planner/references/severity-rules.md +16 -0
- package/bundled/skills/bug-planner/scripts/validate-bug-list.py +4 -8
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +34 -39
- package/bundled/skills/feature-pipeline-launcher/SKILL.md +49 -36
- package/bundled/skills/feature-pipeline-launcher/scripts/preflight-check.py +3 -3
- package/bundled/skills/feature-planner/SKILL.md +53 -142
- package/bundled/skills/feature-planner/assets/evaluation-guide.md +1 -1
- package/bundled/skills/feature-planner/assets/planning-guide.md +21 -5
- package/bundled/skills/feature-planner/references/browser-interaction.md +2 -4
- package/bundled/skills/feature-planner/references/completeness-review.md +57 -0
- package/bundled/skills/feature-planner/references/error-recovery.md +16 -35
- package/bundled/skills/feature-planner/references/incremental-feature-planning.md +1 -1
- package/bundled/skills/feature-planner/references/new-project-planning.md +2 -2
- package/bundled/skills/feature-planner/scripts/validate-and-generate.py +19 -20
- package/bundled/skills/feature-workflow/SKILL.md +24 -25
- package/bundled/skills/prizm-kit/SKILL.md +39 -49
- package/bundled/skills/prizmkit-code-review/SKILL.md +51 -64
- package/bundled/skills/prizmkit-code-review/rules/dimensions.md +85 -0
- package/bundled/skills/prizmkit-code-review/rules/fix-strategy.md +11 -11
- package/bundled/skills/prizmkit-committer/SKILL.md +3 -31
- package/bundled/skills/prizmkit-deploy/SKILL.md +34 -31
- package/bundled/skills/prizmkit-deploy/assets/deploy-template.md +1 -1
- package/bundled/skills/prizmkit-implement/SKILL.md +35 -68
- package/bundled/skills/prizmkit-init/SKILL.md +112 -65
- package/bundled/skills/prizmkit-init/assets/project-brief-template.md +82 -0
- package/bundled/skills/prizmkit-plan/SKILL.md +120 -79
- package/bundled/skills/prizmkit-plan/assets/plan-template.md +28 -18
- package/bundled/skills/prizmkit-plan/assets/spec-template.md +28 -11
- package/bundled/skills/prizmkit-plan/references/clarify-guide.md +3 -3
- package/bundled/skills/prizmkit-plan/references/verification-checklist.md +60 -0
- package/bundled/skills/prizmkit-prizm-docs/SKILL.md +10 -81
- package/bundled/skills/prizmkit-prizm-docs/assets/{PRIZM-SPEC.md → prizm-docs-format.md} +41 -526
- package/bundled/skills/prizmkit-prizm-docs/references/op-init.md +46 -0
- package/bundled/skills/prizmkit-prizm-docs/references/op-rebuild.md +16 -0
- package/bundled/skills/prizmkit-prizm-docs/references/op-status.md +14 -0
- package/bundled/skills/prizmkit-prizm-docs/references/op-update.md +19 -0
- package/bundled/skills/prizmkit-prizm-docs/references/op-validate.md +17 -0
- package/bundled/skills/prizmkit-retrospective/SKILL.md +27 -65
- package/bundled/skills/prizmkit-retrospective/references/knowledge-injection-steps.md +3 -4
- package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +7 -25
- package/bundled/skills/recovery-workflow/SKILL.md +22 -22
- package/bundled/skills/recovery-workflow/evals/evals.json +5 -5
- package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +43 -10
- package/bundled/skills/refactor-pipeline-launcher/SKILL.md +48 -40
- package/bundled/skills/refactor-planner/SKILL.md +43 -61
- package/bundled/skills/refactor-planner/scripts/validate-and-generate-refactor.py +17 -17
- package/bundled/skills/refactor-workflow/SKILL.md +23 -24
- package/bundled/team/prizm-dev-team.json +1 -1
- package/bundled/{skills/prizm-kit/assets → templates}/project-memory-template.md +1 -1
- package/package.json +1 -1
- package/src/clean.js +3 -4
- package/src/gitignore-template.js +7 -9
- package/src/scaffold.js +14 -5
- package/bundled/dev-pipeline/templates/agent-prompts/reviewer-analyze.md +0 -5
- package/bundled/dev-pipeline/templates/sections/phase-analyze-agent.md +0 -19
- package/bundled/dev-pipeline/templates/sections/phase-analyze-full.md +0 -19
- package/bundled/skills/app-planner/references/project-conventions.md +0 -93
- package/bundled/skills/prizmkit-analyze/SKILL.md +0 -207
- package/bundled/skills/prizmkit-code-review/rules/dimensions-bugfix.md +0 -25
- package/bundled/skills/prizmkit-code-review/rules/dimensions-feature.md +0 -43
- package/bundled/skills/prizmkit-code-review/rules/dimensions-refactor.md +0 -25
- package/bundled/skills/prizmkit-implement/references/deploy-guide-protocol.md +0 -69
- package/bundled/skills/prizmkit-verify/SKILL.md +0 -281
- package/bundled/skills/prizmkit-verify/scripts/verify-light.py +0 -402
|
@@ -60,12 +60,12 @@ def parse_args():
|
|
|
60
60
|
parser.add_argument(
|
|
61
61
|
"--feature-list",
|
|
62
62
|
required=True,
|
|
63
|
-
help="Path to the feature-list.json file",
|
|
63
|
+
help="Path to the .prizmkit/plans/feature-list.json file",
|
|
64
64
|
)
|
|
65
65
|
parser.add_argument(
|
|
66
66
|
"--state-dir",
|
|
67
67
|
required=True,
|
|
68
|
-
help="Path to the state
|
|
68
|
+
help="Path to the state directory (default: .prizmkit/state/features)",
|
|
69
69
|
)
|
|
70
70
|
parser.add_argument(
|
|
71
71
|
"--action",
|
|
@@ -159,7 +159,7 @@ def load_feature_status(state_dir, feature_id, feature_list_status=None):
|
|
|
159
159
|
If the file does not exist, return a default pending status.
|
|
160
160
|
|
|
161
161
|
If feature_list_status is a terminal status (completed, failed, skipped),
|
|
162
|
-
it overrides the status field from status.json. This makes feature-list.json
|
|
162
|
+
it overrides the status field from status.json. This makes .prizmkit/plans/feature-list.json
|
|
163
163
|
the single source of truth for terminal statuses, while all other fields
|
|
164
164
|
(retry_count, sessions, etc.) still come from status.json.
|
|
165
165
|
"""
|
|
@@ -195,7 +195,7 @@ def load_feature_status(state_dir, feature_id, feature_list_status=None):
|
|
|
195
195
|
"created_at": now,
|
|
196
196
|
"updated_at": now,
|
|
197
197
|
}
|
|
198
|
-
# feature-list.json wins for terminal statuses
|
|
198
|
+
# .prizmkit/plans/feature-list.json wins for terminal statuses
|
|
199
199
|
if feature_list_status in TERMINAL_STATUSES:
|
|
200
200
|
data["status"] = feature_list_status
|
|
201
201
|
return data
|
|
@@ -210,7 +210,7 @@ def save_feature_status(state_dir, feature_id, status_data):
|
|
|
210
210
|
|
|
211
211
|
|
|
212
212
|
def update_feature_in_list(feature_list_path, feature_id, new_status):
|
|
213
|
-
"""Update a feature's status field in feature-list.json.
|
|
213
|
+
"""Update a feature's status field in .prizmkit/plans/feature-list.json.
|
|
214
214
|
|
|
215
215
|
Reads the whole file, modifies the target feature's status, writes back.
|
|
216
216
|
Returns an error string on failure, None on success.
|
|
@@ -226,7 +226,7 @@ def update_feature_in_list(feature_list_path, feature_id, new_status):
|
|
|
226
226
|
found = True
|
|
227
227
|
break
|
|
228
228
|
if not found:
|
|
229
|
-
return "Feature '{}' not found in feature-list.json".format(feature_id)
|
|
229
|
+
return "Feature '{}' not found in .prizmkit/plans/feature-list.json".format(feature_id)
|
|
230
230
|
return write_json_file(feature_list_path, data)
|
|
231
231
|
|
|
232
232
|
|
|
@@ -336,13 +336,13 @@ def auto_skip_blocked_features(feature_list_path, state_dir, failed_feature_id):
|
|
|
336
336
|
by marking those blocked features as auto_skipped, allowing the pipeline to
|
|
337
337
|
continue processing unblocked features and eventually reach PIPELINE_COMPLETE.
|
|
338
338
|
|
|
339
|
-
Re-reads feature-list.json from disk to get the latest state (including the
|
|
339
|
+
Re-reads .prizmkit/plans/feature-list.json from disk to get the latest state (including the
|
|
340
340
|
just-written failed status from update_feature_in_list).
|
|
341
341
|
|
|
342
|
-
NOTE: This function performs a read-modify-write on feature-list.json without
|
|
343
|
-
file locking. The caller (action_update) also writes to feature-list.json
|
|
342
|
+
NOTE: This function performs a read-modify-write on .prizmkit/plans/feature-list.json without
|
|
343
|
+
file locking. The caller (action_update) also writes to .prizmkit/plans/feature-list.json
|
|
344
344
|
immediately before calling this. Safe for single-pipeline execution, but if
|
|
345
|
-
multiple pipeline instances share the same feature-list.json concurrently,
|
|
345
|
+
multiple pipeline instances share the same .prizmkit/plans/feature-list.json concurrently,
|
|
346
346
|
a race condition may cause lost writes. Add file locking if parallel pipelines
|
|
347
347
|
are introduced.
|
|
348
348
|
"""
|
|
@@ -383,7 +383,7 @@ def auto_skip_blocked_features(feature_list_path, state_dir, failed_feature_id):
|
|
|
383
383
|
if not to_skip:
|
|
384
384
|
return []
|
|
385
385
|
|
|
386
|
-
# Batch-write to feature-list.json
|
|
386
|
+
# Batch-write to .prizmkit/plans/feature-list.json
|
|
387
387
|
for f in features:
|
|
388
388
|
if isinstance(f, dict) and f.get("id") in to_skip:
|
|
389
389
|
f["status"] = "auto_skipped"
|
|
@@ -605,26 +605,31 @@ def action_update(args, feature_list_path, state_dir):
|
|
|
605
605
|
fs["resume_from_phase"] = None
|
|
606
606
|
err = update_feature_in_list(feature_list_path, feature_id, "completed")
|
|
607
607
|
if err:
|
|
608
|
-
error_out("Failed to update feature-list.json: {}".format(err))
|
|
608
|
+
error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
|
|
609
609
|
return
|
|
610
610
|
elif session_status in ("commit_missing", "docs_missing", "merge_conflict"):
|
|
611
|
-
# Degraded outcome: keep artifacts for retry
|
|
611
|
+
# Degraded outcome: keep artifacts for retry.
|
|
612
|
+
# Store granular reason in status.json (internal state),
|
|
613
|
+
# but write only schema-valid status to feature-list.json.
|
|
612
614
|
fs["retry_count"] = fs.get("retry_count", 0) + 1
|
|
613
615
|
|
|
614
616
|
if fs["retry_count"] >= max_retries:
|
|
615
617
|
fs["status"] = "failed"
|
|
616
618
|
target_status = "failed"
|
|
617
619
|
else:
|
|
620
|
+
# status.json keeps the granular degraded reason for diagnostics
|
|
618
621
|
fs["status"] = session_status
|
|
619
|
-
|
|
622
|
+
# feature-list.json gets schema-valid "pending" (will be retried)
|
|
623
|
+
target_status = "pending"
|
|
620
624
|
|
|
625
|
+
fs["degraded_reason"] = session_status
|
|
621
626
|
fs["resume_from_phase"] = None
|
|
622
627
|
fs["sessions"] = []
|
|
623
628
|
fs["last_session_id"] = None
|
|
624
629
|
|
|
625
630
|
err = update_feature_in_list(feature_list_path, feature_id, target_status)
|
|
626
631
|
if err:
|
|
627
|
-
error_out("Failed to update feature-list.json: {}".format(err))
|
|
632
|
+
error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
|
|
628
633
|
return
|
|
629
634
|
else:
|
|
630
635
|
# crashed / failed / timed_out — preserve all artifacts for debugging.
|
|
@@ -646,7 +651,7 @@ def action_update(args, feature_list_path, state_dir):
|
|
|
646
651
|
|
|
647
652
|
err = update_feature_in_list(feature_list_path, feature_id, target_status)
|
|
648
653
|
if err:
|
|
649
|
-
error_out("Failed to update feature-list.json: {}".format(err))
|
|
654
|
+
error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
|
|
650
655
|
return
|
|
651
656
|
|
|
652
657
|
if session_status == "success" and session_id:
|
|
@@ -707,11 +712,12 @@ BOX_WIDTH = 68
|
|
|
707
712
|
|
|
708
713
|
|
|
709
714
|
def _calc_feature_duration(state_dir, feature_id):
|
|
710
|
-
"""
|
|
715
|
+
"""Calculate the duration (in seconds) of a completed feature.
|
|
711
716
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
717
|
+
Computes duration from status.json's created_at and updated_at fields.
|
|
718
|
+
If session records exist, attempts to use the first session's started_at
|
|
719
|
+
to the last update time for the calculation.
|
|
720
|
+
Returns None if the duration cannot be calculated.
|
|
715
721
|
"""
|
|
716
722
|
fs_path = os.path.join(state_dir, "features", feature_id, "status.json")
|
|
717
723
|
if not os.path.isfile(fs_path):
|
|
@@ -730,7 +736,7 @@ def _calc_feature_duration(state_dir, feature_id):
|
|
|
730
736
|
t_start = datetime.strptime(created_at, fmt)
|
|
731
737
|
t_end = datetime.strptime(updated_at, fmt)
|
|
732
738
|
delta = (t_end - t_start).total_seconds()
|
|
733
|
-
#
|
|
739
|
+
# Filter outliers: ignore durations less than 10s or more than 24h
|
|
734
740
|
if delta < 10 or delta > 86400:
|
|
735
741
|
return None
|
|
736
742
|
return delta
|
|
@@ -739,7 +745,7 @@ def _calc_feature_duration(state_dir, feature_id):
|
|
|
739
745
|
|
|
740
746
|
|
|
741
747
|
def _format_duration(seconds):
|
|
742
|
-
"""
|
|
748
|
+
"""Format seconds into a human-readable duration string."""
|
|
743
749
|
if seconds is None:
|
|
744
750
|
return "N/A"
|
|
745
751
|
seconds = int(seconds)
|
|
@@ -756,17 +762,18 @@ def _format_duration(seconds):
|
|
|
756
762
|
|
|
757
763
|
|
|
758
764
|
def _estimate_remaining_time(features, state_dir, counts, feature_list_data=None):
|
|
759
|
-
"""
|
|
765
|
+
"""Estimate remaining time based on completed feature durations, weighted by complexity.
|
|
760
766
|
|
|
761
|
-
|
|
762
|
-
1.
|
|
763
|
-
2.
|
|
764
|
-
|
|
767
|
+
Strategy:
|
|
768
|
+
1. Collect durations of all completed features, grouped by complexity
|
|
769
|
+
2. For remaining pending/in_progress features, estimate using the average duration
|
|
770
|
+
of the corresponding complexity level
|
|
771
|
+
3. If no historical data exists for a complexity level, fall back to the global average
|
|
765
772
|
|
|
766
|
-
|
|
767
|
-
confidence: "high" (>=50%
|
|
773
|
+
Returns an (estimated_seconds, confidence) tuple.
|
|
774
|
+
confidence: "high" (>=50% completed), "medium" (>=25%), "low" (<25%)
|
|
768
775
|
"""
|
|
769
|
-
#
|
|
776
|
+
# Complexity weights (used for estimation when no historical data is available)
|
|
770
777
|
COMPLEXITY_WEIGHT = {"low": 1.0, "medium": 2.0, "high": 4.0}
|
|
771
778
|
|
|
772
779
|
# Build feature-list status map for terminal status override
|
|
@@ -776,7 +783,7 @@ def _estimate_remaining_time(features, state_dir, counts, feature_list_data=None
|
|
|
776
783
|
if isinstance(f, dict) and f.get("id"):
|
|
777
784
|
fl_status_map[f["id"]] = f.get("status")
|
|
778
785
|
|
|
779
|
-
#
|
|
786
|
+
# Collect completed feature durations grouped by complexity
|
|
780
787
|
duration_by_complexity = {} # complexity -> [duration_seconds]
|
|
781
788
|
feature_complexity_map = {} # feature_id -> complexity
|
|
782
789
|
|
|
@@ -811,13 +818,13 @@ def _estimate_remaining_time(features, state_dir, counts, feature_list_data=None
|
|
|
811
818
|
if not all_durations:
|
|
812
819
|
return None, "low"
|
|
813
820
|
|
|
814
|
-
#
|
|
821
|
+
# Calculate average duration per complexity level
|
|
815
822
|
avg_by_complexity = {}
|
|
816
823
|
for c, durations in duration_by_complexity.items():
|
|
817
824
|
avg_by_complexity[c] = sum(durations) / len(durations)
|
|
818
825
|
global_avg = sum(all_durations) / len(all_durations)
|
|
819
826
|
|
|
820
|
-
#
|
|
827
|
+
# Estimate duration for remaining features
|
|
821
828
|
remaining_seconds = 0.0
|
|
822
829
|
remaining_count = 0
|
|
823
830
|
for feature in features:
|
|
@@ -835,12 +842,12 @@ def _estimate_remaining_time(features, state_dir, counts, feature_list_data=None
|
|
|
835
842
|
if complexity in avg_by_complexity:
|
|
836
843
|
remaining_seconds += avg_by_complexity[complexity]
|
|
837
844
|
else:
|
|
838
|
-
#
|
|
845
|
+
# No historical data for this complexity; estimate using global avg × weight ratio
|
|
839
846
|
weight = COMPLEXITY_WEIGHT.get(complexity, 2.0)
|
|
840
847
|
base_weight = COMPLEXITY_WEIGHT.get("medium", 2.0)
|
|
841
848
|
remaining_seconds += global_avg * (weight / base_weight)
|
|
842
849
|
|
|
843
|
-
#
|
|
850
|
+
# Calculate confidence level
|
|
844
851
|
total = len([f for f in features if isinstance(f, dict) and f.get("id")])
|
|
845
852
|
completed = counts.get("completed", 0)
|
|
846
853
|
if total > 0:
|
|
@@ -860,7 +867,7 @@ def _estimate_remaining_time(features, state_dir, counts, feature_list_data=None
|
|
|
860
867
|
def action_status(feature_list_data, state_dir, feature_filter=None):
|
|
861
868
|
"""Print a formatted overview of all features and their status.
|
|
862
869
|
|
|
863
|
-
Status is read exclusively from feature-list.json (the single source of
|
|
870
|
+
Status is read exclusively from .prizmkit/plans/feature-list.json (the single source of
|
|
864
871
|
truth). state_dir is only used for ETA estimation when session history
|
|
865
872
|
is available.
|
|
866
873
|
"""
|
|
@@ -882,12 +889,10 @@ def action_status(feature_list_data, state_dir, feature_filter=None):
|
|
|
882
889
|
"pending": 0,
|
|
883
890
|
"skipped": 0,
|
|
884
891
|
"auto_skipped": 0,
|
|
885
|
-
"commit_missing": 0,
|
|
886
|
-
"docs_missing": 0,
|
|
887
892
|
}
|
|
888
893
|
feature_lines = []
|
|
889
894
|
|
|
890
|
-
# Build status map from feature-list.json only
|
|
895
|
+
# Build status map from .prizmkit/plans/feature-list.json only
|
|
891
896
|
status_map = {}
|
|
892
897
|
for feature in features:
|
|
893
898
|
if not isinstance(feature, dict):
|
|
@@ -897,6 +902,19 @@ def action_status(feature_list_data, state_dir, feature_filter=None):
|
|
|
897
902
|
continue
|
|
898
903
|
status_map[fid] = feature.get("status", "pending")
|
|
899
904
|
|
|
905
|
+
# Build degraded_reason map from status.json (internal pipeline state)
|
|
906
|
+
degraded_reason_map = {}
|
|
907
|
+
for feature in features:
|
|
908
|
+
if not isinstance(feature, dict):
|
|
909
|
+
continue
|
|
910
|
+
fid = feature.get("id")
|
|
911
|
+
if not fid:
|
|
912
|
+
continue
|
|
913
|
+
fs = load_feature_status(state_dir, fid, feature.get("status"))
|
|
914
|
+
dr = fs.get("degraded_reason")
|
|
915
|
+
if dr:
|
|
916
|
+
degraded_reason_map[fid] = dr
|
|
917
|
+
|
|
900
918
|
for feature in features:
|
|
901
919
|
if not isinstance(feature, dict):
|
|
902
920
|
continue
|
|
@@ -906,6 +924,7 @@ def action_status(feature_list_data, state_dir, feature_filter=None):
|
|
|
906
924
|
continue
|
|
907
925
|
|
|
908
926
|
fstatus = feature.get("status", "pending")
|
|
927
|
+
degraded_reason = degraded_reason_map.get(fid)
|
|
909
928
|
|
|
910
929
|
# Count statuses
|
|
911
930
|
if fstatus in counts:
|
|
@@ -914,7 +933,14 @@ def action_status(feature_list_data, state_dir, feature_filter=None):
|
|
|
914
933
|
counts["pending"] += 1
|
|
915
934
|
|
|
916
935
|
# Build status indicator and color
|
|
917
|
-
|
|
936
|
+
# Show degraded reason via icon when a pending feature has one
|
|
937
|
+
if fstatus == "pending" and degraded_reason == "commit_missing":
|
|
938
|
+
icon = COLOR_RED + "[↑]" + COLOR_RESET
|
|
939
|
+
elif fstatus == "pending" and degraded_reason == "docs_missing":
|
|
940
|
+
icon = COLOR_RED + "[D]" + COLOR_RESET
|
|
941
|
+
elif fstatus == "pending" and degraded_reason == "merge_conflict":
|
|
942
|
+
icon = COLOR_RED + "[⚡]" + COLOR_RESET
|
|
943
|
+
elif fstatus == "completed":
|
|
918
944
|
icon = COLOR_GREEN + "[✓]" + COLOR_RESET
|
|
919
945
|
elif fstatus == "in_progress":
|
|
920
946
|
icon = COLOR_YELLOW + "[→]" + COLOR_RESET
|
|
@@ -924,16 +950,22 @@ def action_status(feature_list_data, state_dir, feature_filter=None):
|
|
|
924
950
|
icon = COLOR_GRAY + "[—]" + COLOR_RESET
|
|
925
951
|
elif fstatus == "auto_skipped":
|
|
926
952
|
icon = COLOR_GRAY + "[⊘]" + COLOR_RESET
|
|
927
|
-
elif fstatus == "commit_missing":
|
|
928
|
-
icon = COLOR_RED + "[↑]" + COLOR_RESET
|
|
929
|
-
elif fstatus == "docs_missing":
|
|
930
|
-
icon = COLOR_RED + "[D]" + COLOR_RESET
|
|
931
953
|
else:
|
|
932
954
|
icon = COLOR_GRAY + "[ ]" + COLOR_RESET
|
|
933
955
|
|
|
934
956
|
# Build detail suffix
|
|
935
957
|
detail = ""
|
|
936
|
-
if fstatus == "pending":
|
|
958
|
+
if fstatus == "pending" and degraded_reason:
|
|
959
|
+
detail = " ({}, retrying)".format(degraded_reason)
|
|
960
|
+
# Also check if blocked by dependencies
|
|
961
|
+
deps = feature.get("dependencies", [])
|
|
962
|
+
blocking = [
|
|
963
|
+
d for d in deps
|
|
964
|
+
if status_map.get(d, "pending") != "completed"
|
|
965
|
+
]
|
|
966
|
+
if blocking:
|
|
967
|
+
detail = " ({}, blocked by {})".format(degraded_reason, ", ".join(blocking))
|
|
968
|
+
elif fstatus == "pending":
|
|
937
969
|
# Check if blocked by dependencies
|
|
938
970
|
deps = feature.get("dependencies", [])
|
|
939
971
|
blocking = [
|
|
@@ -950,6 +982,8 @@ def action_status(feature_list_data, state_dir, feature_filter=None):
|
|
|
950
982
|
]
|
|
951
983
|
if blockers:
|
|
952
984
|
detail = " (auto-skipped: blocked by {})".format(", ".join(blockers))
|
|
985
|
+
elif fstatus == "failed" and degraded_reason:
|
|
986
|
+
detail = " (last failure: {})".format(degraded_reason)
|
|
953
987
|
|
|
954
988
|
# Apply color to the whole line content
|
|
955
989
|
if fstatus == "completed":
|
|
@@ -964,7 +998,7 @@ def action_status(feature_list_data, state_dir, feature_filter=None):
|
|
|
964
998
|
line_content = "{} {} {}{}".format(
|
|
965
999
|
fid, icon, COLOR_RED + title + COLOR_RESET, detail
|
|
966
1000
|
)
|
|
967
|
-
elif
|
|
1001
|
+
elif degraded_reason:
|
|
968
1002
|
line_content = "{} {} {}{}".format(
|
|
969
1003
|
fid, icon, COLOR_RED + title + COLOR_RESET, detail
|
|
970
1004
|
)
|
|
@@ -978,16 +1012,16 @@ def action_status(feature_list_data, state_dir, feature_filter=None):
|
|
|
978
1012
|
total = len(features)
|
|
979
1013
|
completed = counts["completed"]
|
|
980
1014
|
|
|
981
|
-
#
|
|
1015
|
+
# Calculate percentage
|
|
982
1016
|
if total > 0:
|
|
983
1017
|
percent = round(completed / total * 100, 1)
|
|
984
1018
|
else:
|
|
985
1019
|
percent = 0.0
|
|
986
1020
|
|
|
987
|
-
#
|
|
1021
|
+
# Generate progress bar
|
|
988
1022
|
progress_bar = _build_progress_bar(percent, width=24)
|
|
989
1023
|
|
|
990
|
-
#
|
|
1024
|
+
# Estimate remaining time
|
|
991
1025
|
est_remaining, confidence = _estimate_remaining_time(
|
|
992
1026
|
features, state_dir, counts, feature_list_data
|
|
993
1027
|
)
|
|
@@ -998,11 +1032,18 @@ def action_status(feature_list_data, state_dir, feature_filter=None):
|
|
|
998
1032
|
summary_line2 = "Failed: {} | Pending: {} | Skipped: {} | Auto-skipped: {}".format(
|
|
999
1033
|
counts["failed"], counts["pending"], counts["skipped"], counts["auto_skipped"]
|
|
1000
1034
|
)
|
|
1001
|
-
|
|
1002
|
-
|
|
1035
|
+
|
|
1036
|
+
# Count degraded features (pending with a degraded_reason from status.json)
|
|
1037
|
+
degraded_count = sum(
|
|
1038
|
+
1 for fid, dr in degraded_reason_map.items()
|
|
1039
|
+
if status_map.get(fid) == "pending" and dr
|
|
1003
1040
|
)
|
|
1041
|
+
if degraded_count > 0:
|
|
1042
|
+
summary_line3 = "Degraded (retrying): {}".format(degraded_count)
|
|
1043
|
+
else:
|
|
1044
|
+
summary_line3 = None
|
|
1004
1045
|
|
|
1005
|
-
#
|
|
1046
|
+
# Build estimated remaining time line
|
|
1006
1047
|
CONFIDENCE_ICONS = {"high": "●", "medium": "◐", "low": "○"}
|
|
1007
1048
|
if est_remaining is not None:
|
|
1008
1049
|
eta_str = _format_duration(est_remaining)
|
|
@@ -1021,7 +1062,8 @@ def action_status(feature_list_data, state_dir, feature_filter=None):
|
|
|
1021
1062
|
print("║" + pad_right(" Project: {}".format(app_name), inner) + " ║")
|
|
1022
1063
|
print("║" + pad_right(" {}".format(summary_line), inner) + " ║")
|
|
1023
1064
|
print("║" + pad_right(" {}".format(summary_line2), inner) + " ║")
|
|
1024
|
-
|
|
1065
|
+
if summary_line3:
|
|
1066
|
+
print("║" + pad_right(" {}".format(summary_line3), inner) + " ║")
|
|
1025
1067
|
print("╠" + "─" * BOX_WIDTH + "╣")
|
|
1026
1068
|
print("║" + pad_right(" Progress: {}".format(progress_bar), inner) + " ║")
|
|
1027
1069
|
print("║" + pad_right(" {}".format(eta_line), inner) + " ║")
|
|
@@ -1038,7 +1080,7 @@ def action_status(feature_list_data, state_dir, feature_filter=None):
|
|
|
1038
1080
|
def action_start(args, feature_list_path, state_dir):
|
|
1039
1081
|
"""Mark a feature as in_progress at session start.
|
|
1040
1082
|
|
|
1041
|
-
This keeps feature-list.json/state status in sync during execution,
|
|
1083
|
+
This keeps .prizmkit/plans/feature-list.json/state status in sync during execution,
|
|
1042
1084
|
instead of only updating after session end.
|
|
1043
1085
|
"""
|
|
1044
1086
|
feature_id = args.feature_id
|
|
@@ -1059,7 +1101,7 @@ def action_start(args, feature_list_path, state_dir):
|
|
|
1059
1101
|
|
|
1060
1102
|
err = update_feature_in_list(feature_list_path, feature_id, "in_progress")
|
|
1061
1103
|
if err:
|
|
1062
|
-
error_out("Failed to update feature-list.json: {}".format(err))
|
|
1104
|
+
error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
|
|
1063
1105
|
return
|
|
1064
1106
|
|
|
1065
1107
|
result = {
|
|
@@ -1080,7 +1122,7 @@ def action_reset(args, feature_list_path, state_dir):
|
|
|
1080
1122
|
"""Reset a feature to pending state.
|
|
1081
1123
|
|
|
1082
1124
|
Resets status.json (status -> pending, retry_count -> 0, clear sessions,
|
|
1083
|
-
clear resume_from_phase) and updates feature-list.json status to pending.
|
|
1125
|
+
clear resume_from_phase) and updates .prizmkit/plans/feature-list.json status to pending.
|
|
1084
1126
|
Does NOT delete any files on disk.
|
|
1085
1127
|
"""
|
|
1086
1128
|
feature_id = args.feature_id
|
|
@@ -1107,10 +1149,10 @@ def action_reset(args, feature_list_path, state_dir):
|
|
|
1107
1149
|
error_out("Failed to save feature status: {}".format(err))
|
|
1108
1150
|
return
|
|
1109
1151
|
|
|
1110
|
-
# Update feature-list.json
|
|
1152
|
+
# Update .prizmkit/plans/feature-list.json
|
|
1111
1153
|
err = update_feature_in_list(feature_list_path, feature_id, "pending")
|
|
1112
1154
|
if err:
|
|
1113
|
-
error_out("Failed to update feature-list.json: {}".format(err))
|
|
1155
|
+
error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
|
|
1114
1156
|
return
|
|
1115
1157
|
|
|
1116
1158
|
result = {
|
|
@@ -1202,7 +1244,7 @@ def action_clean(args, feature_list_path, state_dir):
|
|
|
1202
1244
|
|
|
1203
1245
|
err = update_feature_in_list(feature_list_path, feature_id, "pending")
|
|
1204
1246
|
if err:
|
|
1205
|
-
error_out("Failed to update feature-list.json: {}".format(err))
|
|
1247
|
+
error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
|
|
1206
1248
|
return
|
|
1207
1249
|
|
|
1208
1250
|
result = {
|
|
@@ -1248,7 +1290,7 @@ def action_unskip(args, feature_list_path, state_dir):
|
|
|
1248
1290
|
target = f
|
|
1249
1291
|
break
|
|
1250
1292
|
if not target:
|
|
1251
|
-
error_out("Feature '{}' not found in feature-list.json".format(feature_id))
|
|
1293
|
+
error_out("Feature '{}' not found in .prizmkit/plans/feature-list.json".format(feature_id))
|
|
1252
1294
|
return
|
|
1253
1295
|
if target.get("status") not in ("failed", "skipped", "auto_skipped"):
|
|
1254
1296
|
error_out(
|
|
@@ -1328,7 +1370,7 @@ def action_unskip(args, feature_list_path, state_dir):
|
|
|
1328
1370
|
error_out("No features to unskip")
|
|
1329
1371
|
return
|
|
1330
1372
|
|
|
1331
|
-
# Reset all collected features in feature-list.json
|
|
1373
|
+
# Reset all collected features in .prizmkit/plans/feature-list.json
|
|
1332
1374
|
reset_details = []
|
|
1333
1375
|
for f in features:
|
|
1334
1376
|
if isinstance(f, dict) and f.get("id") in to_reset:
|
|
@@ -1342,7 +1384,7 @@ def action_unskip(args, feature_list_path, state_dir):
|
|
|
1342
1384
|
|
|
1343
1385
|
err = write_json_file(feature_list_path, data)
|
|
1344
1386
|
if err:
|
|
1345
|
-
error_out("Failed to write feature-list.json: {}".format(err))
|
|
1387
|
+
error_out("Failed to write .prizmkit/plans/feature-list.json: {}".format(err))
|
|
1346
1388
|
return
|
|
1347
1389
|
|
|
1348
1390
|
# Reset status.json for each feature
|