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.
Files changed (132) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/adapters/codebuddy/skill-adapter.js +21 -7
  3. package/bundled/agents/prizm-dev-team-reviewer.md +53 -173
  4. package/bundled/dev-pipeline/.env.example +45 -0
  5. package/bundled/dev-pipeline/README.md +64 -64
  6. package/bundled/dev-pipeline/SCHEMA_ANALYSIS.md +535 -0
  7. package/bundled/dev-pipeline/assets/feature-list-example.json +0 -1
  8. package/bundled/dev-pipeline/launch-bugfix-daemon.sh +64 -18
  9. package/bundled/dev-pipeline/launch-feature-daemon.sh +15 -12
  10. package/bundled/dev-pipeline/launch-refactor-daemon.sh +64 -18
  11. package/bundled/dev-pipeline/lib/branch.sh +6 -1
  12. package/bundled/dev-pipeline/lib/common.sh +71 -0
  13. package/bundled/dev-pipeline/lib/heartbeat.sh +2 -2
  14. package/bundled/dev-pipeline/reset-bug.sh +10 -9
  15. package/bundled/dev-pipeline/reset-feature.sh +9 -8
  16. package/bundled/dev-pipeline/reset-refactor.sh +10 -9
  17. package/bundled/dev-pipeline/retry-bugfix.sh +67 -29
  18. package/bundled/dev-pipeline/retry-feature.sh +54 -18
  19. package/bundled/dev-pipeline/retry-refactor.sh +112 -29
  20. package/bundled/dev-pipeline/run-bugfix.sh +281 -59
  21. package/bundled/dev-pipeline/run-feature.sh +53 -18
  22. package/bundled/dev-pipeline/run-refactor.sh +392 -66
  23. package/bundled/dev-pipeline/scripts/check-session-status.py +24 -1
  24. package/bundled/dev-pipeline/scripts/cleanup-logs.py +2 -2
  25. package/bundled/dev-pipeline/scripts/detect-stuck.py +195 -85
  26. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +57 -33
  27. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +25 -9
  28. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +104 -17
  29. package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +34 -9
  30. package/bundled/dev-pipeline/scripts/init-pipeline.py +10 -10
  31. package/bundled/dev-pipeline/scripts/init-refactor-pipeline.py +19 -8
  32. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +1 -5
  33. package/bundled/dev-pipeline/scripts/patch-completion-notes.py +191 -0
  34. package/bundled/dev-pipeline/scripts/update-bug-status.py +167 -22
  35. package/bundled/dev-pipeline/scripts/update-feature-status.py +104 -62
  36. package/bundled/dev-pipeline/scripts/update-refactor-status.py +351 -21
  37. package/bundled/dev-pipeline/templates/agent-prompts/dev-fix.md +1 -1
  38. package/bundled/dev-pipeline/templates/agent-prompts/reviewer-review.md +7 -11
  39. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +41 -7
  40. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +27 -3
  41. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +43 -19
  42. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +54 -26
  43. package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +6 -15
  44. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +36 -25
  45. package/bundled/dev-pipeline/templates/feature-list-schema.json +109 -31
  46. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +270 -0
  47. package/bundled/dev-pipeline/templates/refactor-list-schema.json +11 -3
  48. package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +3 -1
  49. package/bundled/dev-pipeline/templates/sections/critical-paths-agent.md +1 -0
  50. package/bundled/dev-pipeline/templates/sections/feature-context.md +2 -0
  51. package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +29 -2
  52. package/bundled/dev-pipeline/templates/sections/phase-commit.md +22 -0
  53. package/bundled/dev-pipeline/templates/sections/phase-deploy-verification.md +2 -2
  54. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +8 -6
  55. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +7 -5
  56. package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +3 -3
  57. package/bundled/skills/_metadata.json +5 -22
  58. package/bundled/skills/app-planner/SKILL.md +98 -72
  59. package/bundled/skills/app-planner/assets/app-design-guide.md +1 -1
  60. package/bundled/skills/app-planner/references/architecture-decisions.md +1 -1
  61. package/bundled/skills/app-planner/references/project-brief-guide.md +69 -66
  62. package/bundled/skills/bug-fix-workflow/SKILL.md +52 -9
  63. package/bundled/skills/bug-planner/SKILL.md +139 -197
  64. package/bundled/skills/bug-planner/assets/bug-confirmation-template.md +43 -0
  65. package/bundled/skills/bug-planner/references/critic-and-verification.md +44 -0
  66. package/bundled/skills/bug-planner/references/error-recovery.md +73 -0
  67. package/bundled/skills/bug-planner/references/input-formats.md +53 -0
  68. package/bundled/skills/bug-planner/references/schema-validation.md +25 -0
  69. package/bundled/skills/bug-planner/references/severity-rules.md +16 -0
  70. package/bundled/skills/bug-planner/scripts/validate-bug-list.py +4 -8
  71. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +34 -39
  72. package/bundled/skills/feature-pipeline-launcher/SKILL.md +49 -36
  73. package/bundled/skills/feature-pipeline-launcher/scripts/preflight-check.py +3 -3
  74. package/bundled/skills/feature-planner/SKILL.md +53 -142
  75. package/bundled/skills/feature-planner/assets/evaluation-guide.md +1 -1
  76. package/bundled/skills/feature-planner/assets/planning-guide.md +21 -5
  77. package/bundled/skills/feature-planner/references/browser-interaction.md +2 -4
  78. package/bundled/skills/feature-planner/references/completeness-review.md +57 -0
  79. package/bundled/skills/feature-planner/references/error-recovery.md +16 -35
  80. package/bundled/skills/feature-planner/references/incremental-feature-planning.md +1 -1
  81. package/bundled/skills/feature-planner/references/new-project-planning.md +2 -2
  82. package/bundled/skills/feature-planner/scripts/validate-and-generate.py +19 -20
  83. package/bundled/skills/feature-workflow/SKILL.md +24 -25
  84. package/bundled/skills/prizm-kit/SKILL.md +39 -49
  85. package/bundled/skills/prizmkit-code-review/SKILL.md +51 -64
  86. package/bundled/skills/prizmkit-code-review/rules/dimensions.md +85 -0
  87. package/bundled/skills/prizmkit-code-review/rules/fix-strategy.md +11 -11
  88. package/bundled/skills/prizmkit-committer/SKILL.md +3 -31
  89. package/bundled/skills/prizmkit-deploy/SKILL.md +34 -31
  90. package/bundled/skills/prizmkit-deploy/assets/deploy-template.md +1 -1
  91. package/bundled/skills/prizmkit-implement/SKILL.md +35 -68
  92. package/bundled/skills/prizmkit-init/SKILL.md +112 -65
  93. package/bundled/skills/prizmkit-init/assets/project-brief-template.md +82 -0
  94. package/bundled/skills/prizmkit-plan/SKILL.md +120 -79
  95. package/bundled/skills/prizmkit-plan/assets/plan-template.md +28 -18
  96. package/bundled/skills/prizmkit-plan/assets/spec-template.md +28 -11
  97. package/bundled/skills/prizmkit-plan/references/clarify-guide.md +3 -3
  98. package/bundled/skills/prizmkit-plan/references/verification-checklist.md +60 -0
  99. package/bundled/skills/prizmkit-prizm-docs/SKILL.md +10 -81
  100. package/bundled/skills/prizmkit-prizm-docs/assets/{PRIZM-SPEC.md → prizm-docs-format.md} +41 -526
  101. package/bundled/skills/prizmkit-prizm-docs/references/op-init.md +46 -0
  102. package/bundled/skills/prizmkit-prizm-docs/references/op-rebuild.md +16 -0
  103. package/bundled/skills/prizmkit-prizm-docs/references/op-status.md +14 -0
  104. package/bundled/skills/prizmkit-prizm-docs/references/op-update.md +19 -0
  105. package/bundled/skills/prizmkit-prizm-docs/references/op-validate.md +17 -0
  106. package/bundled/skills/prizmkit-retrospective/SKILL.md +27 -65
  107. package/bundled/skills/prizmkit-retrospective/references/knowledge-injection-steps.md +3 -4
  108. package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +7 -25
  109. package/bundled/skills/recovery-workflow/SKILL.md +22 -22
  110. package/bundled/skills/recovery-workflow/evals/evals.json +5 -5
  111. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +43 -10
  112. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +48 -40
  113. package/bundled/skills/refactor-planner/SKILL.md +43 -61
  114. package/bundled/skills/refactor-planner/scripts/validate-and-generate-refactor.py +17 -17
  115. package/bundled/skills/refactor-workflow/SKILL.md +23 -24
  116. package/bundled/team/prizm-dev-team.json +1 -1
  117. package/bundled/{skills/prizm-kit/assets → templates}/project-memory-template.md +1 -1
  118. package/package.json +1 -1
  119. package/src/clean.js +3 -4
  120. package/src/gitignore-template.js +7 -9
  121. package/src/scaffold.js +14 -5
  122. package/bundled/dev-pipeline/templates/agent-prompts/reviewer-analyze.md +0 -5
  123. package/bundled/dev-pipeline/templates/sections/phase-analyze-agent.md +0 -19
  124. package/bundled/dev-pipeline/templates/sections/phase-analyze-full.md +0 -19
  125. package/bundled/skills/app-planner/references/project-conventions.md +0 -93
  126. package/bundled/skills/prizmkit-analyze/SKILL.md +0 -207
  127. package/bundled/skills/prizmkit-code-review/rules/dimensions-bugfix.md +0 -25
  128. package/bundled/skills/prizmkit-code-review/rules/dimensions-feature.md +0 -43
  129. package/bundled/skills/prizmkit-code-review/rules/dimensions-refactor.md +0 -25
  130. package/bundled/skills/prizmkit-implement/references/deploy-guide-protocol.md +0 -69
  131. package/bundled/skills/prizmkit-verify/SKILL.md +0 -281
  132. 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/ directory",
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 and expose specific status.
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
- target_status = session_status
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
- """计算已完成 Feature 的耗时(秒)。
715
+ """Calculate the duration (in seconds) of a completed feature.
711
716
 
712
- 通过 status.json created_at updated_at 来计算。
713
- 如果有 session 记录,尝试用第一个 session started_at 到最后更新时间来计算。
714
- 返回 None 如果无法计算。
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
- # 过滤异常值:少于 10 秒或超过 24 小时的忽略
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
- """基于已完成 Feature 的历史耗时,按 complexity 加权预估剩余时间。
765
+ """Estimate remaining time based on completed feature durations, weighted by complexity.
760
766
 
761
- 策略:
762
- 1. 收集所有已完成 Feature 的耗时,按 complexity 分组
763
- 2. 对剩余的 pending/in_progress Feature,用对应 complexity 的平均耗时估算
764
- 3. 如果某个 complexity 没有历史数据,用全局平均值兜底
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
- 返回 (estimated_seconds, confidence) 元组。
767
- confidence: "high" (>=50% 已完成), "medium" (>=25%), "low" (<25%)
773
+ Returns an (estimated_seconds, confidence) tuple.
774
+ confidence: "high" (>=50% completed), "medium" (>=25%), "low" (<25%)
768
775
  """
769
- # complexity 权重(用于没有历史数据时的估算)
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
- # complexity 分组收集已完成 Feature 的耗时
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
- # 计算各 complexity 的平均耗时
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
- # 估算剩余 Feature 的耗时
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
- # 没有该 complexity 的历史数据,用全局均值 × 权重比例估算
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
- if fstatus == "completed":
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 fstatus in ("commit_missing", "docs_missing"):
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
- summary_line3 = "Commit Missing: {} | Docs Missing: {}".format(
1002
- counts["commit_missing"], counts["docs_missing"]
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
- print("║" + pad_right(" {}".format(summary_line3), inner) + " ║")
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