prizmkit 1.1.100 → 1.1.102

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 (317) hide show
  1. package/bin/create-prizmkit.js +4 -4
  2. package/bundled/VERSION.json +3 -3
  3. package/bundled/dev-pipeline/.env.example +3 -3
  4. package/bundled/dev-pipeline/README.md +110 -90
  5. package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +1 -1
  6. package/bundled/dev-pipeline/cli.py +27 -0
  7. package/bundled/dev-pipeline/prizmkit_runtime/__init__.py +13 -0
  8. package/bundled/dev-pipeline/prizmkit_runtime/__main__.py +9 -0
  9. package/bundled/dev-pipeline/prizmkit_runtime/cli.py +193 -0
  10. package/bundled/dev-pipeline/prizmkit_runtime/commands.py +313 -0
  11. package/bundled/dev-pipeline/prizmkit_runtime/compat.py +32 -0
  12. package/bundled/dev-pipeline/prizmkit_runtime/config.py +410 -0
  13. package/bundled/dev-pipeline/prizmkit_runtime/daemon.py +478 -0
  14. package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +734 -0
  15. package/bundled/dev-pipeline/prizmkit_runtime/heartbeat.py +256 -0
  16. package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +109 -0
  17. package/bundled/dev-pipeline/prizmkit_runtime/paths.py +110 -0
  18. package/bundled/dev-pipeline/prizmkit_runtime/processes.py +373 -0
  19. package/bundled/dev-pipeline/prizmkit_runtime/reset.py +389 -0
  20. package/bundled/dev-pipeline/prizmkit_runtime/runner_bookkeeping.py +51 -0
  21. package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +179 -0
  22. package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +355 -0
  23. package/bundled/dev-pipeline/prizmkit_runtime/runner_prompts.py +118 -0
  24. package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +187 -0
  25. package/bundled/dev-pipeline/prizmkit_runtime/runner_status.py +153 -0
  26. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +425 -0
  27. package/bundled/dev-pipeline/prizmkit_runtime/sessions.py +560 -0
  28. package/bundled/dev-pipeline/prizmkit_runtime/status.py +71 -0
  29. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +51 -36
  30. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +20 -5
  31. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +2 -2
  32. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +39 -0
  33. package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +8 -2
  34. package/bundled/dev-pipeline/templates/agent-prompts/reviewer-review.md +1 -1
  35. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +6 -2
  36. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +14 -6
  37. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +14 -4
  38. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +14 -4
  39. package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +2 -0
  40. package/bundled/dev-pipeline/templates/sections/log-size-awareness.md +9 -30
  41. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +15 -7
  42. package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +42 -29
  43. package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +41 -39
  44. package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +8 -17
  45. package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +2 -2
  46. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +1 -3
  47. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +1 -3
  48. package/bundled/dev-pipeline/templates/sections/phase0-test-baseline.md +3 -8
  49. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-agent.md +6 -2
  50. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-lite.md +5 -1
  51. package/bundled/dev-pipeline/tests/test_auto_skip.py +436 -14
  52. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +115 -7
  53. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +40 -0
  54. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +1226 -0
  55. package/bundled/dev-pipeline/tests/test_unified_cli.py +1045 -0
  56. package/bundled/skills/_metadata.json +1 -1
  57. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +29 -34
  58. package/bundled/skills/bugfix-pipeline-launcher/references/configuration.md +6 -5
  59. package/bundled/skills/feature-pipeline-launcher/SKILL.md +29 -35
  60. package/bundled/skills/feature-pipeline-launcher/references/configuration.md +7 -6
  61. package/bundled/skills/feature-workflow/SKILL.md +3 -6
  62. package/bundled/skills/recovery-workflow/SKILL.md +23 -32
  63. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +29 -35
  64. package/bundled/skills/refactor-pipeline-launcher/references/configuration.md +7 -6
  65. package/bundled/skills/refactor-workflow/SKILL.md +3 -6
  66. package/bundled/templates/hooks/commit-intent.json +2 -2
  67. package/bundled/templates/hooks/diff-prizm-docs.py +226 -0
  68. package/bundled/templates/hooks/prizm-pre-commit.py +45 -0
  69. package/bundled/templates/hooks/validate-prizm-docs.py +240 -0
  70. package/package.json +1 -1
  71. package/src/config.js +42 -19
  72. package/src/external-skills.js +14 -14
  73. package/src/index.js +3 -6
  74. package/src/manifest.js +11 -5
  75. package/src/metadata.js +7 -18
  76. package/src/platforms.js +25 -0
  77. package/src/prompts.js +0 -12
  78. package/src/runtimes.js +18 -11
  79. package/src/scaffold.js +144 -122
  80. package/src/upgrade.js +12 -3
  81. package/bundled/dev-pipeline/launch-bugfix-daemon.sh +0 -500
  82. package/bundled/dev-pipeline/launch-feature-daemon.sh +0 -657
  83. package/bundled/dev-pipeline/launch-refactor-daemon.sh +0 -502
  84. package/bundled/dev-pipeline/lib/branch.sh +0 -326
  85. package/bundled/dev-pipeline/lib/common.sh +0 -1476
  86. package/bundled/dev-pipeline/lib/heartbeat.sh +0 -469
  87. package/bundled/dev-pipeline/reset-bug.sh +0 -425
  88. package/bundled/dev-pipeline/reset-feature.sh +0 -436
  89. package/bundled/dev-pipeline/reset-refactor.sh +0 -423
  90. package/bundled/dev-pipeline/run-bugfix.sh +0 -1430
  91. package/bundled/dev-pipeline/run-feature.sh +0 -1718
  92. package/bundled/dev-pipeline/run-recovery.sh +0 -648
  93. package/bundled/dev-pipeline/run-refactor.sh +0 -1463
  94. package/bundled/dev-pipeline/tests/test-path-resolution.sh +0 -100
  95. package/bundled/dev-pipeline-windows/.env.example +0 -36
  96. package/bundled/dev-pipeline-windows/README.md +0 -30
  97. package/bundled/dev-pipeline-windows/SCHEMA_ANALYSIS.md +0 -533
  98. package/bundled/dev-pipeline-windows/assets/feature-list-example.json +0 -146
  99. package/bundled/dev-pipeline-windows/assets/prizm-dev-team-integration.md +0 -137
  100. package/bundled/dev-pipeline-windows/launch-bugfix-daemon.ps1 +0 -9
  101. package/bundled/dev-pipeline-windows/launch-feature-daemon.ps1 +0 -9
  102. package/bundled/dev-pipeline-windows/launch-refactor-daemon.ps1 +0 -9
  103. package/bundled/dev-pipeline-windows/lib/branch.ps1 +0 -235
  104. package/bundled/dev-pipeline-windows/lib/common.ps1 +0 -991
  105. package/bundled/dev-pipeline-windows/lib/daemon.ps1 +0 -140
  106. package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +0 -1051
  107. package/bundled/dev-pipeline-windows/lib/reset.ps1 +0 -121
  108. package/bundled/dev-pipeline-windows/reset-bug.ps1 +0 -10
  109. package/bundled/dev-pipeline-windows/reset-feature.ps1 +0 -10
  110. package/bundled/dev-pipeline-windows/reset-refactor.ps1 +0 -10
  111. package/bundled/dev-pipeline-windows/run-bugfix.ps1 +0 -9
  112. package/bundled/dev-pipeline-windows/run-feature.ps1 +0 -9
  113. package/bundled/dev-pipeline-windows/run-recovery.ps1 +0 -179
  114. package/bundled/dev-pipeline-windows/run-refactor.ps1 +0 -9
  115. package/bundled/dev-pipeline-windows/scripts/check-session-status.py +0 -301
  116. package/bundled/dev-pipeline-windows/scripts/continuation.py +0 -374
  117. package/bundled/dev-pipeline-windows/scripts/detect-stuck.py +0 -530
  118. package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +0 -2279
  119. package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +0 -763
  120. package/bundled/dev-pipeline-windows/scripts/generate-recovery-prompt.py +0 -805
  121. package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +0 -841
  122. package/bundled/dev-pipeline-windows/scripts/init-bugfix-pipeline.py +0 -316
  123. package/bundled/dev-pipeline-windows/scripts/init-dev-team.py +0 -134
  124. package/bundled/dev-pipeline-windows/scripts/init-pipeline.py +0 -382
  125. package/bundled/dev-pipeline-windows/scripts/init-refactor-pipeline.py +0 -399
  126. package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +0 -1362
  127. package/bundled/dev-pipeline-windows/scripts/patch-completion-notes.py +0 -191
  128. package/bundled/dev-pipeline-windows/scripts/prizmkit-test-gate.py +0 -446
  129. package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +0 -1121
  130. package/bundled/dev-pipeline-windows/scripts/update-checkpoint.py +0 -173
  131. package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +0 -1789
  132. package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +0 -1327
  133. package/bundled/dev-pipeline-windows/scripts/utils.py +0 -542
  134. package/bundled/dev-pipeline-windows/templates/agent-prompts/critic-plan-challenge.md +0 -7
  135. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-fix.md +0 -7
  136. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-implement.md +0 -65
  137. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-resume.md +0 -5
  138. package/bundled/dev-pipeline-windows/templates/agent-prompts/reviewer-review.md +0 -7
  139. package/bundled/dev-pipeline-windows/templates/bootstrap-prompt.md +0 -73
  140. package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +0 -589
  141. package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +0 -710
  142. package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +0 -791
  143. package/bundled/dev-pipeline-windows/templates/bug-fix-list-schema.json +0 -263
  144. package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +0 -326
  145. package/bundled/dev-pipeline-windows/templates/feature-list-schema.json +0 -237
  146. package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +0 -340
  147. package/bundled/dev-pipeline-windows/templates/refactor-list-schema.json +0 -270
  148. package/bundled/dev-pipeline-windows/templates/sections/ac-verification-checklist.md +0 -7
  149. package/bundled/dev-pipeline-windows/templates/sections/checkpoint-system.md +0 -91
  150. package/bundled/dev-pipeline-windows/templates/sections/context-budget-rules.md +0 -34
  151. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-agent.md +0 -10
  152. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-full.md +0 -12
  153. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-lite.md +0 -7
  154. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-agent.md +0 -8
  155. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-full.md +0 -9
  156. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-lite.md +0 -6
  157. package/bundled/dev-pipeline-windows/templates/sections/failure-capture.md +0 -21
  158. package/bundled/dev-pipeline-windows/templates/sections/feature-context.md +0 -21
  159. package/bundled/dev-pipeline-windows/templates/sections/log-size-awareness.md +0 -41
  160. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-auto.md +0 -283
  161. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-opencli.md +0 -112
  162. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification.md +0 -168
  163. package/bundled/dev-pipeline-windows/templates/sections/phase-commit-full.md +0 -82
  164. package/bundled/dev-pipeline-windows/templates/sections/phase-commit.md +0 -75
  165. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-agent-suffix.md +0 -23
  166. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-base.md +0 -14
  167. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-lite-suffix.md +0 -19
  168. package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan-full.md +0 -63
  169. package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan.md +0 -42
  170. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-agent.md +0 -44
  171. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-full.md +0 -59
  172. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-lite.md +0 -39
  173. package/bundled/dev-pipeline-windows/templates/sections/phase-plan-agent.md +0 -27
  174. package/bundled/dev-pipeline-windows/templates/sections/phase-plan-lite.md +0 -27
  175. package/bundled/dev-pipeline-windows/templates/sections/phase-prizmkit-test.md +0 -39
  176. package/bundled/dev-pipeline-windows/templates/sections/phase-review-agent.md +0 -35
  177. package/bundled/dev-pipeline-windows/templates/sections/phase-review-full.md +0 -35
  178. package/bundled/dev-pipeline-windows/templates/sections/phase-specify-plan-full.md +0 -73
  179. package/bundled/dev-pipeline-windows/templates/sections/phase0-init.md +0 -13
  180. package/bundled/dev-pipeline-windows/templates/sections/phase0-test-baseline.md +0 -21
  181. package/bundled/dev-pipeline-windows/templates/sections/session-context.md +0 -5
  182. package/bundled/dev-pipeline-windows/templates/sections/subagent-timeout-recovery.md +0 -6
  183. package/bundled/dev-pipeline-windows/templates/sections/task-contract.md +0 -34
  184. package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-agent.md +0 -48
  185. package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-lite.md +0 -48
  186. package/bundled/dev-pipeline-windows/templates/session-status-schema.json +0 -83
  187. package/bundled/skills-windows/app-planner/SKILL.md +0 -296
  188. package/bundled/skills-windows/app-planner/assets/app-design-guide.md +0 -101
  189. package/bundled/skills-windows/app-planner/references/architecture-decisions.md +0 -52
  190. package/bundled/skills-windows/app-planner/references/brainstorm-guide.md +0 -101
  191. package/bundled/skills-windows/app-planner/references/frontend-design-guide.md +0 -71
  192. package/bundled/skills-windows/app-planner/references/infrastructure-convention-discovery.md +0 -108
  193. package/bundled/skills-windows/app-planner/references/project-brief-guide.md +0 -82
  194. package/bundled/skills-windows/app-planner/references/project-conventions-discovery.md +0 -59
  195. package/bundled/skills-windows/app-planner/references/project-state-detection.md +0 -90
  196. package/bundled/skills-windows/app-planner/references/red-team-checklist.md +0 -40
  197. package/bundled/skills-windows/app-planner/references/rules/backend/derivation-rules.md +0 -619
  198. package/bundled/skills-windows/app-planner/references/rules/backend/fixed-rules.md +0 -285
  199. package/bundled/skills-windows/app-planner/references/rules/backend/question-bank.md +0 -249
  200. package/bundled/skills-windows/app-planner/references/rules/backend/question-manifest.json +0 -46
  201. package/bundled/skills-windows/app-planner/references/rules/backend/template.md +0 -173
  202. package/bundled/skills-windows/app-planner/references/rules/database/derivation-rules.md +0 -382
  203. package/bundled/skills-windows/app-planner/references/rules/database/fixed-rules.md +0 -211
  204. package/bundled/skills-windows/app-planner/references/rules/database/question-bank.md +0 -184
  205. package/bundled/skills-windows/app-planner/references/rules/database/question-manifest.json +0 -39
  206. package/bundled/skills-windows/app-planner/references/rules/database/template.md +0 -158
  207. package/bundled/skills-windows/app-planner/references/rules/frontend/derivation-rules.md +0 -820
  208. package/bundled/skills-windows/app-planner/references/rules/frontend/fixed-rules.md +0 -188
  209. package/bundled/skills-windows/app-planner/references/rules/frontend/question-bank.md +0 -319
  210. package/bundled/skills-windows/app-planner/references/rules/frontend/question-manifest.json +0 -51
  211. package/bundled/skills-windows/app-planner/references/rules/frontend/template.md +0 -339
  212. package/bundled/skills-windows/app-planner/references/rules/mobile/derivation-rules.md +0 -649
  213. package/bundled/skills-windows/app-planner/references/rules/mobile/fixed-rules.md +0 -290
  214. package/bundled/skills-windows/app-planner/references/rules/mobile/question-bank.md +0 -232
  215. package/bundled/skills-windows/app-planner/references/rules/mobile/question-manifest.json +0 -47
  216. package/bundled/skills-windows/app-planner/references/rules/mobile/template.md +0 -175
  217. package/bundled/skills-windows/app-planner/references/rules-configuration.md +0 -46
  218. package/bundled/skills-windows/bug-fix-workflow/SKILL.md +0 -379
  219. package/bundled/skills-windows/bug-fix-workflow/references/bug-diagnosis.md +0 -41
  220. package/bundled/skills-windows/bug-planner/SKILL.md +0 -402
  221. package/bundled/skills-windows/bug-planner/assets/bug-confirmation-template.md +0 -43
  222. package/bundled/skills-windows/bug-planner/references/critic-and-verification.md +0 -44
  223. package/bundled/skills-windows/bug-planner/references/error-recovery.md +0 -73
  224. package/bundled/skills-windows/bug-planner/references/input-formats.md +0 -53
  225. package/bundled/skills-windows/bug-planner/references/schema-validation.md +0 -25
  226. package/bundled/skills-windows/bug-planner/references/severity-rules.md +0 -16
  227. package/bundled/skills-windows/bug-planner/scripts/validate-bug-list.py +0 -322
  228. package/bundled/skills-windows/bugfix-pipeline-launcher/SKILL.md +0 -304
  229. package/bundled/skills-windows/bugfix-pipeline-launcher/references/configuration.md +0 -84
  230. package/bundled/skills-windows/feature-pipeline-launcher/SKILL.md +0 -381
  231. package/bundled/skills-windows/feature-pipeline-launcher/references/configuration.md +0 -67
  232. package/bundled/skills-windows/feature-pipeline-launcher/scripts/preflight-check.py +0 -462
  233. package/bundled/skills-windows/feature-planner/SKILL.md +0 -397
  234. package/bundled/skills-windows/feature-planner/assets/evaluation-guide.md +0 -64
  235. package/bundled/skills-windows/feature-planner/assets/planning-guide.md +0 -214
  236. package/bundled/skills-windows/feature-planner/references/browser-interaction.md +0 -59
  237. package/bundled/skills-windows/feature-planner/references/completeness-review.md +0 -57
  238. package/bundled/skills-windows/feature-planner/references/decomposition-patterns.md +0 -75
  239. package/bundled/skills-windows/feature-planner/references/error-recovery.md +0 -90
  240. package/bundled/skills-windows/feature-planner/references/incremental-feature-planning.md +0 -112
  241. package/bundled/skills-windows/feature-planner/references/new-project-planning.md +0 -85
  242. package/bundled/skills-windows/feature-planner/scripts/validate-and-generate.py +0 -1020
  243. package/bundled/skills-windows/feature-workflow/SKILL.md +0 -374
  244. package/bundled/skills-windows/feature-workflow/references/brainstorm-guide.md +0 -137
  245. package/bundled/skills-windows/prizm-kit/SKILL.md +0 -93
  246. package/bundled/skills-windows/prizmkit-code-review/SKILL.md +0 -161
  247. package/bundled/skills-windows/prizmkit-code-review/references/dev-agent-prompt.md +0 -30
  248. package/bundled/skills-windows/prizmkit-code-review/references/review-report-template.md +0 -31
  249. package/bundled/skills-windows/prizmkit-code-review/references/reviewer-agent-prompt.md +0 -66
  250. package/bundled/skills-windows/prizmkit-code-review/scripts/check_loop.py +0 -186
  251. package/bundled/skills-windows/prizmkit-committer/SKILL.md +0 -89
  252. package/bundled/skills-windows/prizmkit-deploy/SKILL.md +0 -444
  253. package/bundled/skills-windows/prizmkit-deploy/references/ci-cd-workflows.md +0 -115
  254. package/bundled/skills-windows/prizmkit-deploy/references/cloud-platform-deploy.md +0 -93
  255. package/bundled/skills-windows/prizmkit-deploy/references/data-safety-examples.md +0 -120
  256. package/bundled/skills-windows/prizmkit-deploy/references/database-setup.md +0 -46
  257. package/bundled/skills-windows/prizmkit-deploy/references/deploy-config-schema.md +0 -148
  258. package/bundled/skills-windows/prizmkit-deploy/references/deploy-history-schema.md +0 -62
  259. package/bundled/skills-windows/prizmkit-deploy/references/deployment-modes.md +0 -50
  260. package/bundled/skills-windows/prizmkit-deploy/references/direct-upload.md +0 -26
  261. package/bundled/skills-windows/prizmkit-deploy/references/dns-setup.md +0 -42
  262. package/bundled/skills-windows/prizmkit-deploy/references/docker-deploy.md +0 -31
  263. package/bundled/skills-windows/prizmkit-deploy/references/firewall-setup.md +0 -37
  264. package/bundled/skills-windows/prizmkit-deploy/references/live-validation-notes.md +0 -21
  265. package/bundled/skills-windows/prizmkit-deploy/references/nginx-blue-green.md +0 -59
  266. package/bundled/skills-windows/prizmkit-deploy/references/ssh-bootstrap-flow.md +0 -49
  267. package/bundled/skills-windows/prizmkit-deploy/references/ssh-execution-flow.md +0 -41
  268. package/bundled/skills-windows/prizmkit-deploy/references/ssh-takeover.md +0 -20
  269. package/bundled/skills-windows/prizmkit-deploy/references/ssl-setup.md +0 -56
  270. package/bundled/skills-windows/prizmkit-implement/SKILL.md +0 -71
  271. package/bundled/skills-windows/prizmkit-init/SKILL.md +0 -356
  272. package/bundled/skills-windows/prizmkit-init/assets/project-brief-template.md +0 -82
  273. package/bundled/skills-windows/prizmkit-init/references/config-schema.md +0 -70
  274. package/bundled/skills-windows/prizmkit-init/references/rules/layer-detection.md +0 -41
  275. package/bundled/skills-windows/prizmkit-init/references/tech-stack-catalog.md +0 -13
  276. package/bundled/skills-windows/prizmkit-init/references/update-supplement.md +0 -9
  277. package/bundled/skills-windows/prizmkit-plan/SKILL.md +0 -102
  278. package/bundled/skills-windows/prizmkit-plan/assets/plan-template.md +0 -115
  279. package/bundled/skills-windows/prizmkit-plan/assets/spec-template.md +0 -73
  280. package/bundled/skills-windows/prizmkit-plan/references/clarify-guide.md +0 -67
  281. package/bundled/skills-windows/prizmkit-plan/references/examples.md +0 -85
  282. package/bundled/skills-windows/prizmkit-plan/references/verification-checklist.md +0 -60
  283. package/bundled/skills-windows/prizmkit-prizm-docs/SKILL.md +0 -129
  284. package/bundled/skills-windows/prizmkit-prizm-docs/assets/prizm-docs-format.md +0 -613
  285. package/bundled/skills-windows/prizmkit-prizm-docs/references/op-init.md +0 -45
  286. package/bundled/skills-windows/prizmkit-prizm-docs/references/op-rebuild.md +0 -15
  287. package/bundled/skills-windows/prizmkit-prizm-docs/references/op-status.md +0 -14
  288. package/bundled/skills-windows/prizmkit-prizm-docs/references/op-update.md +0 -19
  289. package/bundled/skills-windows/prizmkit-prizm-docs/references/op-validate.md +0 -17
  290. package/bundled/skills-windows/prizmkit-retrospective/SKILL.md +0 -87
  291. package/bundled/skills-windows/prizmkit-retrospective/references/knowledge-injection-steps.md +0 -50
  292. package/bundled/skills-windows/prizmkit-retrospective/references/structural-sync-steps.md +0 -43
  293. package/bundled/skills-windows/prizmkit-test/SKILL.md +0 -227
  294. package/bundled/skills-windows/prizmkit-test/references/boundary-coverage-protocol.md +0 -220
  295. package/bundled/skills-windows/prizmkit-test/references/examples.md +0 -143
  296. package/bundled/skills-windows/prizmkit-test/references/service-boundary-test-catalog.md +0 -225
  297. package/bundled/skills-windows/prizmkit-test/references/test-generation-steps.md +0 -109
  298. package/bundled/skills-windows/prizmkit-test/references/test-report-template.md +0 -111
  299. package/bundled/skills-windows/prizmkit-test/scripts/validate_boundary_report.py +0 -347
  300. package/bundled/skills-windows/recovery-workflow/SKILL.md +0 -355
  301. package/bundled/skills-windows/recovery-workflow/evals/evals.json +0 -46
  302. package/bundled/skills-windows/recovery-workflow/references/detection.md +0 -58
  303. package/bundled/skills-windows/recovery-workflow/scripts/detect-recovery-state.py +0 -544
  304. package/bundled/skills-windows/refactor-pipeline-launcher/SKILL.md +0 -346
  305. package/bundled/skills-windows/refactor-pipeline-launcher/references/configuration.md +0 -72
  306. package/bundled/skills-windows/refactor-planner/SKILL.md +0 -398
  307. package/bundled/skills-windows/refactor-planner/assets/planning-guide.md +0 -292
  308. package/bundled/skills-windows/refactor-planner/references/behavior-preservation.md +0 -301
  309. package/bundled/skills-windows/refactor-planner/references/fast-path.md +0 -59
  310. package/bundled/skills-windows/refactor-planner/references/planning-phases.md +0 -135
  311. package/bundled/skills-windows/refactor-planner/references/refactor-scoping-guide.md +0 -221
  312. package/bundled/skills-windows/refactor-planner/scripts/validate-and-generate-refactor.py +0 -858
  313. package/bundled/skills-windows/refactor-workflow/SKILL.md +0 -343
  314. package/bundled/skills-windows/refactor-workflow/references/brainstorm-guide.md +0 -116
  315. package/bundled/templates/hooks/diff-prizm-docs.sh +0 -158
  316. package/bundled/templates/hooks/prizm-pre-commit.sh +0 -108
  317. package/bundled/templates/hooks/validate-prizm-docs.sh +0 -139
@@ -1,1789 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Core state machine for updating feature status in the dev-pipeline.
3
-
4
- Handles nine actions:
5
- - get_next: Find the next feature to process based on priority and dependencies
6
- - start: Mark a feature as in_progress when a session starts
7
- - update: Update a feature's status based on session outcome
8
- - status: Print a formatted overview of all features
9
- - pause: Save pipeline state for graceful shutdown
10
- - reset: Reset a feature to pending (status + retry count)
11
- - clean: Reset + delete session history + delete prizmkit artifacts
12
- - complete: Shortcut for manually marking a feature as completed
13
- - unskip: Recover auto-skipped features (reset failed/skipped upstream + auto_skipped downstream)
14
-
15
- Usage:
16
- python3 update-feature-status.py \
17
- --feature-list <path> --state-dir <path> \
18
- --action <get_next|start|update|status|pause|reset|clean|complete|unskip> \
19
- [--feature-id <id>] [--session-status <status>] \
20
- [--session-id <id>] [--max-retries <n>] [--max-infra-retries <n>] \
21
- [--features <filter>]
22
- """
23
-
24
- import argparse
25
- import json
26
- import os
27
- import re
28
- import shutil
29
- import sys
30
- from datetime import datetime, timezone
31
-
32
- from utils import (
33
- load_json_file,
34
- write_json_file,
35
- error_out,
36
- pad_right,
37
- _build_progress_bar,
38
- )
39
-
40
-
41
- SESSION_STATUS_VALUES = [
42
- "success",
43
- "partial_resumable",
44
- "partial_not_resumable",
45
- "failed",
46
- "crashed",
47
- "timed_out",
48
- "infra_error",
49
- "context_overflow",
50
- "stalled_context_continuation",
51
- "commit_missing",
52
- "docs_missing",
53
- "merge_conflict",
54
- ]
55
-
56
- TERMINAL_STATUSES = {"completed", "failed", "skipped", "auto_skipped", "split"}
57
-
58
-
59
- def parse_args():
60
- parser = argparse.ArgumentParser(
61
- description="Core state machine for dev-pipeline feature status management."
62
- )
63
- parser.add_argument(
64
- "--feature-list",
65
- required=True,
66
- help="Path to the .prizmkit/plans/feature-list.json file",
67
- )
68
- parser.add_argument(
69
- "--state-dir",
70
- required=True,
71
- help="Path to the state directory (default: .prizmkit/state/features)",
72
- )
73
- parser.add_argument(
74
- "--action",
75
- required=True,
76
- choices=["get_next", "start", "update", "status", "pause", "reset", "clean", "complete", "unskip"],
77
- help="Action to perform",
78
- )
79
- parser.add_argument(
80
- "--feature-id",
81
- default=None,
82
- help="Feature ID (required for start/reset/clean/complete/update actions)",
83
- )
84
- parser.add_argument(
85
- "--session-status",
86
- default=None,
87
- choices=SESSION_STATUS_VALUES,
88
- help="Session outcome status (required for 'update' action)",
89
- )
90
- parser.add_argument(
91
- "--session-id",
92
- default=None,
93
- help="Session ID (optional, for 'update' action)",
94
- )
95
- parser.add_argument(
96
- "--max-retries",
97
- type=int,
98
- default=3,
99
- help="Maximum code retry count before marking as failed (default: 3)",
100
- )
101
- parser.add_argument(
102
- "--max-infra-retries",
103
- type=int,
104
- default=3,
105
- help="Maximum infrastructure retry count before marking as failed (default: 3)",
106
- )
107
- parser.add_argument(
108
- "--feature-slug",
109
- default=None,
110
- help="Feature slug (e.g. 007-import-export-desktop). Required for 'clean' action.",
111
- )
112
- parser.add_argument(
113
- "--project-root",
114
- default=None,
115
- help="Project root directory. Required for 'clean' action.",
116
- )
117
- parser.add_argument(
118
- "--features",
119
- default=None,
120
- help="Feature filter: comma-separated IDs (F-001,F-003) or range (F-001:F-010), or mixed.",
121
- )
122
- parser.add_argument(
123
- "--active-dev-branch",
124
- default=None,
125
- help="Development branch preserved for continuation after context overflow.",
126
- )
127
- parser.add_argument(
128
- "--base-branch",
129
- default=None,
130
- help="Original/base branch for continuation branch reuse.",
131
- )
132
- parser.add_argument(
133
- "--last-fatal-error-code",
134
- default=None,
135
- help="Fatal runtime error code associated with continuation.",
136
- )
137
- parser.add_argument(
138
- "--continuation-summary-path",
139
- default=None,
140
- help="Optional continuation summary artifact path.",
141
- )
142
- parser.add_argument(
143
- "--no-progress-count",
144
- type=int,
145
- default=None,
146
- help="Consecutive context-overflow sessions without objective progress.",
147
- )
148
- parser.add_argument(
149
- "--progress-fingerprint",
150
- default=None,
151
- help="JSON progress fingerprint captured after the session.",
152
- )
153
- return parser.parse_args()
154
-
155
-
156
- def parse_feature_filter(features_str):
157
- """Parse --features argument into a set of feature IDs.
158
-
159
- Supported formats:
160
- F-001,F-003,F-005 -> {"F-001", "F-003", "F-005"}
161
- F-001:F-010 -> {"F-001", "F-002", ..., "F-010"}
162
- F-001,F-005:F-010 -> mixed, union of both
163
-
164
- Returns None if features_str is None/empty (meaning no filter).
165
- """
166
- if not features_str:
167
- return None
168
-
169
- result = set()
170
- for part in features_str.split(","):
171
- part = part.strip()
172
- if not part:
173
- continue
174
- if ":" in part:
175
- tokens = part.split(":", 1)
176
- m_start = re.search(r"\d+", tokens[0])
177
- m_end = re.search(r"\d+", tokens[1])
178
- if not m_start or not m_end:
179
- error_out("Invalid range format: {}".format(part))
180
- start_num = int(m_start.group())
181
- end_num = int(m_end.group())
182
- if start_num > end_num:
183
- start_num, end_num = end_num, start_num
184
- for i in range(start_num, end_num + 1):
185
- result.add("F-{:03d}".format(i))
186
- else:
187
- result.add(part.upper())
188
- return result if result else None
189
-
190
-
191
- def now_iso():
192
- """Return the current UTC time in ISO8601 format."""
193
- return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
194
-
195
-
196
- def load_feature_status(state_dir, feature_id):
197
- """Load the runtime state from status.json for a feature.
198
-
199
- Returns runtime fields only (retry_count, sessions, etc.).
200
- The 'status' field is NOT included — status lives exclusively
201
- in feature-list.json.
202
- """
203
- status_path = os.path.join(state_dir, feature_id, "status.json")
204
- if not os.path.isfile(status_path):
205
- now = now_iso()
206
- return {
207
- "feature_id": feature_id,
208
- "retry_count": 0,
209
- "max_retries": 3,
210
- "max_infra_retries": 3,
211
- "infra_error_count": 0,
212
- "last_infra_error_session_id": None,
213
- "sessions": [],
214
- "last_session_id": None,
215
- "resume_from_phase": None,
216
- "created_at": now,
217
- "updated_at": now,
218
- }
219
- data, err = load_json_file(status_path)
220
- if err:
221
- now = now_iso()
222
- return {
223
- "feature_id": feature_id,
224
- "retry_count": 0,
225
- "max_retries": 3,
226
- "max_infra_retries": 3,
227
- "infra_error_count": 0,
228
- "last_infra_error_session_id": None,
229
- "sessions": [],
230
- "last_session_id": None,
231
- "resume_from_phase": None,
232
- "created_at": now,
233
- "updated_at": now,
234
- }
235
- # Defensively remove status if present (legacy data)
236
- data.pop("status", None)
237
- return data
238
-
239
-
240
- def save_feature_status(state_dir, feature_id, status_data):
241
- """Write the status.json for a feature (runtime fields only)."""
242
- # Defensively strip status — it belongs in feature-list.json
243
- status_data.pop("status", None)
244
- status_path = os.path.join(state_dir, feature_id, "status.json")
245
- return write_json_file(status_path, status_data)
246
-
247
-
248
- def get_feature_status_from_list(feature_list_path, feature_id):
249
- """Read a single feature's status from feature-list.json."""
250
- data, err = load_json_file(feature_list_path)
251
- if err:
252
- return "pending"
253
- for f in data.get("features", []):
254
- if isinstance(f, dict) and f.get("id") == feature_id:
255
- return f.get("status", "pending")
256
- return "pending"
257
-
258
-
259
- def update_feature_in_list(feature_list_path, feature_id, new_status):
260
- """Update a feature's status field in .prizmkit/plans/feature-list.json.
261
-
262
- Reads the whole file, modifies the target feature's status, writes back.
263
- Returns an error string on failure, None on success.
264
- """
265
- data, err = load_json_file(feature_list_path)
266
- if err:
267
- return err
268
- features = data.get("features", [])
269
- found = False
270
- for feature in features:
271
- if isinstance(feature, dict) and feature.get("id") == feature_id:
272
- feature["status"] = new_status
273
- found = True
274
- break
275
- if not found:
276
- return "Feature '{}' not found in .prizmkit/plans/feature-list.json".format(feature_id)
277
- return write_json_file(feature_list_path, data)
278
-
279
-
280
- CONTINUATION_PENDING_FIELDS = [
281
- "active_dev_branch",
282
- "base_branch",
283
- "continuation_pending",
284
- "continuation_reason",
285
- "last_context_overflow_session_id",
286
- "previous_session_id",
287
- "last_fatal_error_code",
288
- "continuation_summary_path",
289
- "no_progress_count",
290
- "last_progress_fingerprint",
291
- "needs_attention",
292
- "stalled_context_continuation",
293
- "stalled_reason",
294
- ]
295
-
296
- CONTINUATION_COUNTER_FIELDS = [
297
- "context_overflow_count",
298
- "continuation_count",
299
- ]
300
-
301
-
302
- def has_pending_continuation_metadata(status_data):
303
- """Return True when runtime state still has pending continuation metadata."""
304
- return any(key in status_data for key in CONTINUATION_PENDING_FIELDS)
305
-
306
-
307
- def clear_pending_continuation_metadata(status_data):
308
- """Clear branch/pending continuation fields while preserving observability counters."""
309
- for key in CONTINUATION_PENDING_FIELDS:
310
- status_data.pop(key, None)
311
-
312
-
313
- def reset_continuation_metadata(status_data):
314
- """Clear all continuation runtime metadata, including manual-reset counters."""
315
- clear_pending_continuation_metadata(status_data)
316
- for key in CONTINUATION_COUNTER_FIELDS:
317
- status_data.pop(key, None)
318
-
319
-
320
- STALL_REASON = "context_overflow_without_git_checkpoint_or_artifact_progress"
321
-
322
-
323
- def _load_progress_fingerprint(raw):
324
- if not raw:
325
- return None
326
- try:
327
- return json.loads(raw)
328
- except (TypeError, ValueError):
329
- return {"unparsed": str(raw)}
330
-
331
-
332
- def _set_continuation_common(status_data, args, session_id):
333
- if args.active_dev_branch:
334
- status_data["active_dev_branch"] = args.active_dev_branch
335
- if args.base_branch:
336
- status_data["base_branch"] = args.base_branch
337
- status_data["last_context_overflow_session_id"] = session_id
338
- status_data["previous_session_id"] = session_id
339
- status_data["last_fatal_error_code"] = args.last_fatal_error_code or "context_overflow"
340
- if args.continuation_summary_path:
341
- status_data["continuation_summary_path"] = args.continuation_summary_path
342
- else:
343
- status_data.pop("continuation_summary_path", None)
344
- if args.no_progress_count is not None:
345
- status_data["no_progress_count"] = max(0, args.no_progress_count)
346
- fingerprint = _load_progress_fingerprint(args.progress_fingerprint)
347
- if fingerprint is not None:
348
- status_data["last_progress_fingerprint"] = fingerprint
349
- status_data.pop("last_infra_error_session_id", None)
350
- status_data["resume_from_phase"] = None
351
- if session_id:
352
- status_data["last_session_id"] = session_id
353
-
354
-
355
- def set_context_overflow_continuation_metadata(status_data, args, session_id):
356
- """Record continuation metadata for a context-overflow outcome in status.json."""
357
- status_data["context_overflow_count"] = status_data.get("context_overflow_count", 0) + 1
358
- status_data["continuation_count"] = status_data.get("continuation_count", 0) + 1
359
- status_data["continuation_pending"] = True
360
- status_data["continuation_reason"] = "context_overflow"
361
- status_data.pop("needs_attention", None)
362
- status_data.pop("stalled_context_continuation", None)
363
- status_data.pop("stalled_reason", None)
364
- _set_continuation_common(status_data, args, session_id)
365
-
366
-
367
- def set_stalled_context_continuation_metadata(status_data, args, session_id):
368
- """Record no-progress context-overflow stall without consuming retry budgets."""
369
- status_data["context_overflow_count"] = status_data.get("context_overflow_count", 0) + 1
370
- status_data["continuation_count"] = status_data.get("continuation_count", 0) + 1
371
- _set_continuation_common(status_data, args, session_id)
372
- status_data["continuation_pending"] = False
373
- status_data["continuation_reason"] = STALL_REASON
374
- status_data["needs_attention"] = True
375
- status_data["stalled_context_continuation"] = True
376
- status_data["stalled_reason"] = STALL_REASON
377
- if args.no_progress_count is None or args.no_progress_count < 2:
378
- status_data["no_progress_count"] = 2
379
-
380
-
381
- def continuation_metadata_summary(status_data):
382
- """Return additive continuation fields for CLI JSON outputs."""
383
- return {
384
- "active_dev_branch": status_data.get("active_dev_branch"),
385
- "base_branch": status_data.get("base_branch"),
386
- "continuation_pending": status_data.get("continuation_pending", False),
387
- "continuation_reason": status_data.get("continuation_reason"),
388
- "last_context_overflow_session_id": status_data.get("last_context_overflow_session_id"),
389
- "last_fatal_error_code": status_data.get("last_fatal_error_code"),
390
- "previous_session_id": status_data.get("previous_session_id"),
391
- "context_overflow_count": status_data.get("context_overflow_count", 0),
392
- "continuation_count": status_data.get("continuation_count", 0),
393
- "no_progress_count": status_data.get("no_progress_count", 0),
394
- "last_progress_fingerprint": status_data.get("last_progress_fingerprint"),
395
- "needs_attention": status_data.get("needs_attention", False),
396
- "stalled_context_continuation": status_data.get("stalled_context_continuation", False),
397
- "stalled_reason": status_data.get("stalled_reason"),
398
- "continuation_summary_path": status_data.get("continuation_summary_path"),
399
- }
400
-
401
-
402
- def _default_project_root():
403
- # Script lives at <pipeline>/scripts/update-feature-status.py.
404
- # Pipeline may be either <project>/.prizmkit/dev-pipeline (user install)
405
- # or <repo>/dev-pipeline (framework source). Auto-detect by checking if
406
- # the parent of dev-pipeline is named ".prizmkit".
407
- env = os.environ.get("PROJECT_ROOT")
408
- if env:
409
- return os.path.abspath(env)
410
- script_dir = os.path.dirname(os.path.abspath(__file__))
411
- pipeline_dir = os.path.dirname(script_dir)
412
- pipeline_parent = os.path.dirname(pipeline_dir)
413
- if os.path.basename(pipeline_parent) == ".prizmkit":
414
- return os.path.dirname(pipeline_parent)
415
- return pipeline_parent
416
-
417
-
418
- def _build_feature_slug(feature_id, title):
419
- numeric = feature_id.replace("F-", "").replace("f-", "").zfill(3)
420
- cleaned = re.sub(r"[^a-z0-9\s-]", "", (title or "").lower())
421
- cleaned = re.sub(r"[\s]+", "-", cleaned.strip())
422
- cleaned = re.sub(r"-+", "-", cleaned).strip("-")
423
- if not cleaned:
424
- cleaned = "feature"
425
- return "{}-{}".format(numeric, cleaned)
426
-
427
-
428
- def _get_feature_slug(feature_list_path, feature_id):
429
- data, err = load_json_file(feature_list_path)
430
- if err:
431
- return None
432
- for feature in data.get("features", []):
433
- if isinstance(feature, dict) and feature.get("id") == feature_id:
434
- return _build_feature_slug(feature_id, feature.get("title", ""))
435
- return None
436
-
437
-
438
- def _resolve_project_file(project_root, path):
439
- if not path:
440
- return None
441
- if os.path.isabs(path):
442
- target = os.path.abspath(path)
443
- else:
444
- target = os.path.abspath(os.path.join(project_root, path))
445
- root = os.path.abspath(project_root)
446
- try:
447
- rel = os.path.relpath(target, root)
448
- except ValueError:
449
- return None
450
- if rel == os.pardir or rel.startswith(os.pardir + os.sep):
451
- return None
452
- return target
453
-
454
-
455
- def cleanup_continuation_artifacts(project_root, status_data, candidate_paths=None):
456
- """Delete durable continuation handoff files for a manual clean reset."""
457
- candidates = []
458
- if isinstance(status_data, dict):
459
- candidates.append(status_data.get("continuation_summary_path"))
460
- candidates.extend(candidate_paths or [])
461
-
462
- cleaned = []
463
- seen = set()
464
- for candidate in candidates:
465
- target = _resolve_project_file(project_root, candidate)
466
- if not target or target in seen:
467
- continue
468
- seen.add(target)
469
- if os.path.basename(target) != "continuation-summary.md":
470
- continue
471
- if os.path.isfile(target):
472
- os.remove(target)
473
- cleaned.append("Deleted continuation artifact {}".format(target))
474
- return cleaned
475
-
476
-
477
- def cleanup_feature_artifacts(feature_list_path, state_dir, feature_id, project_root=None):
478
- """Delete intermediate artifacts for a failed feature run.
479
-
480
- Cleans session history, per-feature transient state, generated specs,
481
- current-session pointer, and .dev-team workspace to avoid context pollution.
482
- """
483
- if not project_root:
484
- project_root = _default_project_root()
485
-
486
- cleaned = []
487
-
488
- # 1) Remove all session history
489
- sessions_dir = os.path.join(state_dir, feature_id, "sessions")
490
- sessions_deleted = 0
491
- if os.path.isdir(sessions_dir):
492
- for entry in os.listdir(sessions_dir):
493
- entry_path = os.path.join(sessions_dir, entry)
494
- if os.path.isdir(entry_path):
495
- shutil.rmtree(entry_path)
496
- sessions_deleted += 1
497
- cleaned.append("Deleted {} session(s) from {}".format(sessions_deleted, sessions_dir))
498
-
499
- # 2) Remove transient files under feature state dir (keep status.json)
500
- feature_dir = os.path.join(state_dir, feature_id)
501
- if os.path.isdir(feature_dir):
502
- for entry in os.listdir(feature_dir):
503
- if entry == "status.json" or entry == "sessions":
504
- continue
505
- entry_path = os.path.join(feature_dir, entry)
506
- if os.path.isdir(entry_path):
507
- shutil.rmtree(entry_path)
508
- cleaned.append("Deleted directory {}".format(entry_path))
509
- elif os.path.isfile(entry_path):
510
- os.remove(entry_path)
511
- cleaned.append("Deleted file {}".format(entry_path))
512
-
513
- # 3) Remove generated prizm specs for this feature
514
- feature_slug = _get_feature_slug(feature_list_path, feature_id)
515
- if feature_slug:
516
- specs_dir = os.path.join(project_root, ".prizmkit", "specs", feature_slug)
517
- if os.path.isdir(specs_dir):
518
- file_count = sum(len(files) for _, _, files in os.walk(specs_dir))
519
- shutil.rmtree(specs_dir)
520
- cleaned.append("Deleted {} ({} files)".format(specs_dir, file_count))
521
-
522
- # 4) Remove global dev-team workspace to avoid stale context contamination
523
- dev_team_dir = os.path.join(project_root, ".dev-team")
524
- if os.path.isdir(dev_team_dir):
525
- file_count = sum(len(files) for _, _, files in os.walk(dev_team_dir))
526
- shutil.rmtree(dev_team_dir)
527
- cleaned.append("Deleted {} ({} files)".format(dev_team_dir, file_count))
528
-
529
- # 5) Clear current-session pointer if it points to this feature
530
- # (no-op: current-session.json has been removed from the pipeline)
531
-
532
- return cleaned
533
-
534
-
535
- def load_session_status(state_dir, feature_id, session_id):
536
- """Load a session's session-status.json file."""
537
- session_status_path = os.path.join(state_dir, feature_id, "sessions",
538
- session_id, "session-status.json"
539
- )
540
- data, err = load_json_file(session_status_path)
541
- if err:
542
- return None, err
543
- return data, None
544
-
545
-
546
- # ---------------------------------------------------------------------------
547
- # Auto-skip: cascade failure to blocked downstream features
548
- # ---------------------------------------------------------------------------
549
-
550
- def auto_skip_blocked_features(feature_list_path, state_dir, failed_feature_id):
551
- """Recursively mark all downstream features blocked by a failed feature as auto_skipped.
552
-
553
- When a feature is marked as failed, any feature whose dependency chain includes
554
- the failed feature can never be executed. This function propagates the failure
555
- by marking those blocked features as auto_skipped, allowing the pipeline to
556
- continue processing unblocked features and eventually reach PIPELINE_COMPLETE.
557
-
558
- Re-reads .prizmkit/plans/feature-list.json from disk to get the latest state (including the
559
- just-written failed status from update_feature_in_list).
560
-
561
- NOTE: This function performs a read-modify-write on .prizmkit/plans/feature-list.json without
562
- file locking. The caller (action_update) also writes to .prizmkit/plans/feature-list.json
563
- immediately before calling this. Safe for single-pipeline execution, but if
564
- multiple pipeline instances share the same .prizmkit/plans/feature-list.json concurrently,
565
- a race condition may cause lost writes. Add file locking if parallel pipelines
566
- are introduced.
567
- """
568
- data, err = load_json_file(feature_list_path)
569
- if err:
570
- return []
571
- features = data.get("features", [])
572
-
573
- # Build current status map
574
- status_map = {}
575
- for f in features:
576
- if isinstance(f, dict) and f.get("id"):
577
- status_map[f["id"]] = f.get("status", "pending")
578
-
579
- # Collect all features to auto-skip (recursive propagation)
580
- to_skip = set()
581
- changed = True
582
- while changed:
583
- changed = False
584
- for f in features:
585
- if not isinstance(f, dict):
586
- continue
587
- fid = f.get("id")
588
- if not fid or fid in to_skip:
589
- continue
590
- current = status_map.get(fid, "pending")
591
- if current in TERMINAL_STATUSES:
592
- continue
593
- deps = f.get("dependencies", [])
594
- for dep_id in deps:
595
- dep_status = status_map.get(dep_id, "pending")
596
- if dep_status in ("failed", "skipped", "auto_skipped") or dep_id in to_skip:
597
- to_skip.add(fid)
598
- status_map[fid] = "auto_skipped"
599
- changed = True
600
- break
601
-
602
- if not to_skip:
603
- return []
604
-
605
- # Batch-write to .prizmkit/plans/feature-list.json
606
- for f in features:
607
- if isinstance(f, dict) and f.get("id") in to_skip:
608
- f["status"] = "auto_skipped"
609
- write_json_file(feature_list_path, data)
610
-
611
- # Update timestamps in status.json for each auto-skipped feature
612
- for fid in to_skip:
613
- fs = load_feature_status(state_dir, fid)
614
- fs["updated_at"] = now_iso()
615
- save_feature_status(state_dir, fid, fs)
616
-
617
- # Build blocking reason map for logging
618
- skipped_info = []
619
- for f in features:
620
- if not isinstance(f, dict):
621
- continue
622
- fid = f.get("id")
623
- if fid not in to_skip:
624
- continue
625
- deps = f.get("dependencies", [])
626
- blockers = [
627
- d for d in deps
628
- if d == failed_feature_id or d in to_skip
629
- ]
630
- skipped_info.append({
631
- "feature_id": fid,
632
- "title": f.get("title", ""),
633
- "blocked_by": blockers,
634
- })
635
-
636
- print(
637
- "[auto-skip] {} feature(s) auto-skipped due to failed {}:".format(
638
- len(skipped_info), failed_feature_id
639
- ),
640
- file=sys.stderr,
641
- )
642
- for info in skipped_info:
643
- print(
644
- " {} ({}) — blocked by {}".format(
645
- info["feature_id"],
646
- info["title"],
647
- ", ".join(info["blocked_by"]),
648
- ),
649
- file=sys.stderr,
650
- )
651
-
652
- return skipped_info
653
-
654
-
655
- # ---------------------------------------------------------------------------
656
- # Action: get_next
657
- # ---------------------------------------------------------------------------
658
-
659
- def action_get_next(feature_list_data, state_dir, feature_filter=None):
660
- """Find the next feature to process.
661
-
662
- Priority logic:
663
- 1. Skip terminal statuses (completed, failed, skipped, auto_skipped, split)
664
- 2. If feature_filter is set, skip features not in the filter
665
- 3. Check that all dependencies are completed
666
- 4. Prefer in_progress features over pending ones (interrupted session resume)
667
- 5. Among eligible features, pick highest priority (high > medium > low)
668
- """
669
- features = feature_list_data.get("features", [])
670
- if not features:
671
- print("PIPELINE_COMPLETE")
672
- return
673
-
674
- # Build status map from ALL features (for dependency checking).
675
- # Status comes from feature-list.json (the single source of truth).
676
- # This must happen BEFORE the feature filter is applied, because
677
- # filtered features may depend on features outside the filter.
678
- status_map = {} # feature_id -> status string
679
- status_data_map = {} # feature_id -> runtime status data (retry_count, etc.)
680
- for feature in features:
681
- if not isinstance(feature, dict):
682
- continue
683
- fid = feature.get("id")
684
- if not fid:
685
- continue
686
- status_map[fid] = feature.get("status", "pending")
687
- fs = load_feature_status(state_dir, fid)
688
- status_data_map[fid] = fs
689
-
690
- # Apply feature filter: only consider these features as candidates
691
- # for execution, but dependency checking still uses the full status_map
692
- if feature_filter is not None:
693
- features = [
694
- f for f in features
695
- if isinstance(f, dict) and f.get("id") in feature_filter
696
- ]
697
- if not features:
698
- print("PIPELINE_COMPLETE")
699
- return
700
-
701
- # Check if all features are in terminal state
702
- non_terminal = [
703
- f for f in features
704
- if isinstance(f, dict) and f.get("id")
705
- and status_map.get(f["id"], "pending") not in TERMINAL_STATUSES
706
- ]
707
- if not non_terminal:
708
- print("PIPELINE_COMPLETE")
709
- return
710
-
711
- # Find eligible features (dependencies all completed)
712
- eligible = []
713
- has_remaining = False
714
- for feature in non_terminal:
715
- fid = feature.get("id")
716
- if not fid:
717
- continue
718
- has_remaining = True
719
- deps = feature.get("dependencies", [])
720
- all_deps_completed = True
721
- for dep_id in deps:
722
- if status_map.get(dep_id, "pending") != "completed":
723
- all_deps_completed = False
724
- break
725
- if all_deps_completed:
726
- eligible.append(feature)
727
-
728
- if not eligible:
729
- if has_remaining:
730
- print("PIPELINE_BLOCKED")
731
- else:
732
- print("PIPELINE_COMPLETE")
733
- return
734
-
735
- # Separate in_progress from pending
736
- in_progress_features = []
737
- pending_features = []
738
- for feature in eligible:
739
- fid = feature.get("id")
740
- fstatus = status_map.get(fid, "pending")
741
- if fstatus == "in_progress":
742
- in_progress_features.append(feature)
743
- else:
744
- pending_features.append(feature)
745
-
746
- # Priority mapping: string enum → sort order (critical first)
747
- _PRIORITY_ORDER = {"critical": 0, "high": 1, "medium": 2, "low": 3}
748
-
749
- # Prefer in_progress features, then pending; sort by priority (high > medium > low)
750
- if in_progress_features:
751
- candidates = sorted(
752
- in_progress_features,
753
- key=lambda f: _PRIORITY_ORDER.get(f.get("priority", "low"), 3)
754
- )
755
- else:
756
- candidates = sorted(
757
- pending_features,
758
- key=lambda f: _PRIORITY_ORDER.get(f.get("priority", "low"), 3)
759
- )
760
-
761
- chosen = candidates[0]
762
- chosen_id = chosen["id"]
763
- chosen_status_data = status_data_map.get(chosen_id, {})
764
-
765
- result = {
766
- "feature_id": chosen_id,
767
- "title": chosen.get("title", ""),
768
- "retry_count": chosen_status_data.get("retry_count", 0),
769
- "infra_error_count": chosen_status_data.get("infra_error_count", 0),
770
- "resume_from_phase": chosen_status_data.get("resume_from_phase", None),
771
- }
772
- result.update(continuation_metadata_summary(chosen_status_data))
773
- print(json.dumps(result, indent=2, ensure_ascii=False))
774
-
775
-
776
- # ---------------------------------------------------------------------------
777
- # Action: update
778
- # ---------------------------------------------------------------------------
779
-
780
- def action_update(args, feature_list_path, state_dir):
781
- """Update a feature's status based on session outcome.
782
-
783
- Failure policy:
784
- - Never continue from partial/failed session context
785
- - Always clean intermediate artifacts and restart from scratch
786
- """
787
- feature_id = args.feature_id
788
- session_status = args.session_status
789
- session_id = args.session_id
790
- max_retries = args.max_retries
791
- max_infra_retries = args.max_infra_retries
792
-
793
- if not feature_id:
794
- error_out("--feature-id is required for 'update' action")
795
- return
796
- if not session_status:
797
- error_out("--session-status is required for 'update' action")
798
- return
799
-
800
- fs = load_feature_status(state_dir, feature_id)
801
- current_list_status = get_feature_status_from_list(feature_list_path, feature_id)
802
-
803
- # Track what status we write to feature-list.json
804
- new_status = current_list_status
805
-
806
- if session_status == "success":
807
- fs["infra_error_count"] = 0
808
- fs["last_infra_error_session_id"] = None
809
- pending_continuation_metadata = has_pending_continuation_metadata(fs)
810
- clear_pending_continuation_metadata(fs)
811
- # No-op guard: if this exact successful session was already recorded,
812
- # avoid rewriting state files again (prevents post-commit dirty changes).
813
- existing_sessions = fs.get("sessions", [])
814
- already_completed = current_list_status == "completed" and fs.get("resume_from_phase") is None
815
- same_session_already_recorded = (
816
- session_id
817
- and session_id in existing_sessions
818
- and fs.get("last_session_id") == session_id
819
- )
820
- if already_completed and not pending_continuation_metadata and (same_session_already_recorded or not session_id):
821
- summary = {
822
- "action": "update",
823
- "feature_id": feature_id,
824
- "session_status": session_status,
825
- "new_status": "completed",
826
- "retry_count": fs.get("retry_count", 0),
827
- "infra_error_count": fs.get("infra_error_count", 0),
828
- "max_infra_retries": max_infra_retries,
829
- "resume_from_phase": fs.get("resume_from_phase"),
830
- "updated_at": fs.get("updated_at"),
831
- "no_op": True,
832
- }
833
- print(json.dumps(summary, indent=2, ensure_ascii=False))
834
- return
835
-
836
- new_status = "completed"
837
- fs["resume_from_phase"] = None
838
- err = update_feature_in_list(feature_list_path, feature_id, "completed")
839
- if err:
840
- error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
841
- return
842
- elif session_status in ("commit_missing", "docs_missing", "merge_conflict"):
843
- # Degraded outcome: keep artifacts for retry.
844
- # Store granular reason in status.json (internal state),
845
- # but write only schema-valid status to feature-list.json.
846
- fs["retry_count"] = fs.get("retry_count", 0) + 1
847
-
848
- if fs["retry_count"] >= max_retries:
849
- new_status = "failed"
850
- else:
851
- # feature-list.json gets schema-valid "pending" (will be retried)
852
- new_status = "pending"
853
-
854
- fs["degraded_reason"] = session_status
855
- fs["resume_from_phase"] = None
856
- fs["sessions"] = []
857
- if session_id:
858
- fs["last_session_id"] = session_id
859
- fs["last_failed_session_id"] = session_id
860
-
861
- err = update_feature_in_list(feature_list_path, feature_id, new_status)
862
- if err:
863
- error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
864
- return
865
- elif session_status == "infra_error":
866
- # AI CLI/provider outage, auth failure, gateway error, etc.
867
- # Infra failures do not consume the code retry budget, but they still
868
- # need their own bounded budget so a flaky provider cannot loop forever.
869
- infra_error_count = fs.get("infra_error_count", 0) + 1
870
- fs["infra_error_count"] = infra_error_count
871
- fs["last_infra_error_session_id"] = session_id
872
- fs["max_infra_retries"] = max_infra_retries
873
- fs["degraded_reason"] = session_status
874
- if session_id:
875
- fs["last_session_id"] = session_id
876
- fs["resume_from_phase"] = None
877
-
878
- if infra_error_count >= max_infra_retries:
879
- new_status = "failed"
880
- if session_id:
881
- fs["last_failed_session_id"] = session_id
882
- else:
883
- new_status = "pending"
884
-
885
- err = update_feature_in_list(feature_list_path, feature_id, new_status)
886
- if err:
887
- error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
888
- return
889
- elif session_status == "context_overflow":
890
- # Context overflow is continuation state, not code or infra retry state.
891
- set_context_overflow_continuation_metadata(fs, args, session_id)
892
- new_status = "in_progress"
893
- if current_list_status != "in_progress":
894
- err = update_feature_in_list(feature_list_path, feature_id, "in_progress")
895
- if err:
896
- error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
897
- return
898
- elif session_status == "stalled_context_continuation":
899
- # No-progress context-overflow stall pauses automation without consuming retry budgets.
900
- set_stalled_context_continuation_metadata(fs, args, session_id)
901
- new_status = "failed"
902
- err = update_feature_in_list(feature_list_path, feature_id, new_status)
903
- if err:
904
- error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
905
- return
906
- else:
907
- # crashed / failed / timed_out — preserve all artifacts for debugging.
908
- fs["retry_count"] = fs.get("retry_count", 0) + 1
909
-
910
- if fs["retry_count"] >= max_retries:
911
- new_status = "failed"
912
- else:
913
- new_status = "pending"
914
-
915
- fs["resume_from_phase"] = None
916
- if session_id:
917
- fs["last_session_id"] = session_id
918
- fs["last_failed_session_id"] = session_id
919
- # Keep sessions list and last_session_id for debugging
920
-
921
- err = update_feature_in_list(feature_list_path, feature_id, new_status)
922
- if err:
923
- error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
924
- return
925
-
926
- if session_status == "success" and session_id:
927
- sessions = fs.get("sessions", [])
928
- if session_id not in sessions:
929
- sessions.append(session_id)
930
- fs["sessions"] = sessions
931
- fs["last_session_id"] = session_id
932
-
933
- fs["updated_at"] = now_iso()
934
-
935
- err = save_feature_status(state_dir, feature_id, fs)
936
- if err:
937
- error_out("Failed to save feature status: {}".format(err))
938
- return
939
-
940
- # Auto-skip downstream features when this feature is marked as failed or skipped
941
- auto_skipped_features = []
942
- if new_status in ("failed", "skipped"):
943
- auto_skipped_features = auto_skip_blocked_features(
944
- feature_list_path, state_dir, feature_id
945
- )
946
-
947
- summary = {
948
- "action": "update",
949
- "feature_id": feature_id,
950
- "session_status": session_status,
951
- "new_status": new_status,
952
- "retry_count": fs["retry_count"],
953
- "infra_error_count": fs.get("infra_error_count", 0),
954
- "max_infra_retries": max_infra_retries,
955
- "resume_from_phase": fs.get("resume_from_phase"),
956
- "updated_at": fs["updated_at"],
957
- }
958
- if auto_skipped_features:
959
- summary["auto_skipped"] = [info["feature_id"] for info in auto_skipped_features]
960
- if session_status in ("commit_missing", "docs_missing", "merge_conflict"):
961
- summary["degraded_reason"] = session_status
962
- summary["restart_policy"] = "finalization_retry"
963
- elif session_status == "infra_error":
964
- summary["restart_policy"] = "infra_retry"
965
- summary["infra_error_count"] = fs.get("infra_error_count", 0)
966
- summary["artifacts_preserved"] = True
967
- elif session_status == "context_overflow":
968
- summary["restart_policy"] = "context_overflow_continuation"
969
- summary.update(continuation_metadata_summary(fs))
970
- summary["artifacts_preserved"] = True
971
- elif session_status == "stalled_context_continuation":
972
- summary["restart_policy"] = "needs_attention"
973
- summary.update(continuation_metadata_summary(fs))
974
- summary["artifacts_preserved"] = True
975
- elif session_status != "success":
976
- summary["restart_policy"] = "preserve_and_retry"
977
- summary["artifacts_preserved"] = True
978
-
979
- print(json.dumps(summary, indent=2, ensure_ascii=False))
980
-
981
-
982
- # ---------------------------------------------------------------------------
983
- # Action: status
984
- # ---------------------------------------------------------------------------
985
-
986
- # ANSI color codes
987
- COLOR_GREEN = "\033[92m"
988
- COLOR_YELLOW = "\033[93m"
989
- COLOR_RED = "\033[91m"
990
- COLOR_GRAY = "\033[90m"
991
- COLOR_BOLD = "\033[1m"
992
- COLOR_RESET = "\033[0m"
993
-
994
- BOX_WIDTH = 68
995
-
996
-
997
- def _calc_feature_duration(state_dir, feature_id):
998
- """Calculate the duration (in seconds) of a completed feature.
999
-
1000
- Computes duration from status.json's created_at and updated_at fields.
1001
- If session records exist, attempts to use the first session's started_at
1002
- to the last update time for the calculation.
1003
- Returns None if the duration cannot be calculated.
1004
- """
1005
- fs_path = os.path.join(state_dir, feature_id, "status.json")
1006
- if not os.path.isfile(fs_path):
1007
- return None
1008
- data, err = load_json_file(fs_path)
1009
- if err or not data:
1010
- return None
1011
-
1012
- created_at = data.get("created_at")
1013
- updated_at = data.get("updated_at")
1014
- if not created_at or not updated_at:
1015
- return None
1016
-
1017
- try:
1018
- fmt = "%Y-%m-%dT%H:%M:%SZ"
1019
- t_start = datetime.strptime(created_at, fmt)
1020
- t_end = datetime.strptime(updated_at, fmt)
1021
- delta = (t_end - t_start).total_seconds()
1022
- # Filter outliers: ignore durations less than 10s or more than 24h
1023
- if delta < 10 or delta > 86400:
1024
- return None
1025
- return delta
1026
- except (ValueError, TypeError):
1027
- return None
1028
-
1029
-
1030
- def _format_duration(seconds):
1031
- """Format seconds into a human-readable duration string."""
1032
- if seconds is None:
1033
- return "N/A"
1034
- seconds = int(seconds)
1035
- if seconds < 60:
1036
- return "{}s".format(seconds)
1037
- elif seconds < 3600:
1038
- m = seconds // 60
1039
- s = seconds % 60
1040
- return "{}m{}s".format(m, s)
1041
- else:
1042
- h = seconds // 3600
1043
- m = (seconds % 3600) // 60
1044
- return "{}h{}m".format(h, m)
1045
-
1046
-
1047
- def _estimate_remaining_time(features, state_dir, counts, feature_list_data=None):
1048
- """Estimate remaining time based on completed feature durations, weighted by complexity.
1049
-
1050
- Strategy:
1051
- 1. Collect durations of all completed features, grouped by complexity
1052
- 2. For remaining pending/in_progress features, estimate using the average duration
1053
- of the corresponding complexity level
1054
- 3. If no historical data exists for a complexity level, fall back to the global average
1055
-
1056
- Returns an (estimated_seconds, confidence) tuple.
1057
- confidence: "high" (>=50% completed), "medium" (>=25%), "low" (<25%)
1058
- """
1059
- # Complexity weights (used for estimation when no historical data is available)
1060
- COMPLEXITY_WEIGHT = {"low": 1.0, "medium": 2.0, "high": 4.0}
1061
-
1062
- # Build feature-list status map (status lives in feature-list.json)
1063
- fl_status_map = {}
1064
- if feature_list_data:
1065
- for f in feature_list_data.get("features", []):
1066
- if isinstance(f, dict) and f.get("id"):
1067
- fl_status_map[f["id"]] = f.get("status", "pending")
1068
-
1069
- # Collect completed feature durations grouped by complexity
1070
- duration_by_complexity = {} # complexity -> [duration_seconds]
1071
- feature_complexity_map = {} # feature_id -> complexity
1072
-
1073
- for feature in features:
1074
- if not isinstance(feature, dict):
1075
- continue
1076
- fid = feature.get("id")
1077
- if not fid:
1078
- continue
1079
- complexity = feature.get("estimated_complexity", "medium")
1080
- feature_complexity_map[fid] = complexity
1081
-
1082
- all_durations = []
1083
- for feature in features:
1084
- if not isinstance(feature, dict):
1085
- continue
1086
- fid = feature.get("id")
1087
- if not fid:
1088
- continue
1089
- if fl_status_map.get(fid) != "completed":
1090
- continue
1091
- duration = _calc_feature_duration(state_dir, fid)
1092
- if duration is None:
1093
- continue
1094
- complexity = feature_complexity_map.get(fid, "medium")
1095
- if complexity not in duration_by_complexity:
1096
- duration_by_complexity[complexity] = []
1097
- duration_by_complexity[complexity].append(duration)
1098
- all_durations.append(duration)
1099
-
1100
- if not all_durations:
1101
- return None, "low"
1102
-
1103
- # Calculate average duration per complexity level
1104
- avg_by_complexity = {}
1105
- for c, durations in duration_by_complexity.items():
1106
- avg_by_complexity[c] = sum(durations) / len(durations)
1107
- global_avg = sum(all_durations) / len(all_durations)
1108
-
1109
- # Estimate duration for remaining features
1110
- remaining_seconds = 0.0
1111
- remaining_count = 0
1112
- for feature in features:
1113
- if not isinstance(feature, dict):
1114
- continue
1115
- fid = feature.get("id")
1116
- if not fid:
1117
- continue
1118
- fstatus = fl_status_map.get(fid, "pending")
1119
- if fstatus in TERMINAL_STATUSES:
1120
- continue
1121
- remaining_count += 1
1122
- complexity = feature_complexity_map.get(fid, "medium")
1123
- if complexity in avg_by_complexity:
1124
- remaining_seconds += avg_by_complexity[complexity]
1125
- else:
1126
- # No historical data for this complexity; estimate using global avg × weight ratio
1127
- weight = COMPLEXITY_WEIGHT.get(complexity, 2.0)
1128
- base_weight = COMPLEXITY_WEIGHT.get("medium", 2.0)
1129
- remaining_seconds += global_avg * (weight / base_weight)
1130
-
1131
- # Calculate confidence level
1132
- total = len([f for f in features if isinstance(f, dict) and f.get("id")])
1133
- completed = counts.get("completed", 0)
1134
- if total > 0:
1135
- ratio = completed / total
1136
- if ratio >= 0.5:
1137
- confidence = "high"
1138
- elif ratio >= 0.25:
1139
- confidence = "medium"
1140
- else:
1141
- confidence = "low"
1142
- else:
1143
- confidence = "low"
1144
-
1145
- return remaining_seconds, confidence
1146
-
1147
-
1148
- def action_status(feature_list_data, state_dir, feature_filter=None):
1149
- """Print a formatted overview of all features and their status.
1150
-
1151
- Status is read exclusively from .prizmkit/plans/feature-list.json (the single source of
1152
- truth). state_dir is only used for ETA estimation when session history
1153
- is available.
1154
- """
1155
- features = feature_list_data.get("features", [])
1156
- app_name = feature_list_data.get("project_name", feature_list_data.get("app_name", "Unknown"))
1157
-
1158
- # Apply feature filter
1159
- if feature_filter is not None:
1160
- features = [
1161
- f for f in features
1162
- if isinstance(f, dict) and f.get("id") in feature_filter
1163
- ]
1164
-
1165
- # Gather status info
1166
- counts = {
1167
- "completed": 0,
1168
- "in_progress": 0,
1169
- "failed": 0,
1170
- "pending": 0,
1171
- "skipped": 0,
1172
- "auto_skipped": 0,
1173
- }
1174
- feature_lines = []
1175
-
1176
- # Build status map from .prizmkit/plans/feature-list.json only
1177
- status_map = {}
1178
- for feature in features:
1179
- if not isinstance(feature, dict):
1180
- continue
1181
- fid = feature.get("id")
1182
- if not fid:
1183
- continue
1184
- status_map[fid] = feature.get("status", "pending")
1185
-
1186
- # Build degraded_reason map from status.json (internal pipeline state)
1187
- degraded_reason_map = {}
1188
- for feature in features:
1189
- if not isinstance(feature, dict):
1190
- continue
1191
- fid = feature.get("id")
1192
- if not fid:
1193
- continue
1194
- fs = load_feature_status(state_dir, fid)
1195
- dr = fs.get("degraded_reason")
1196
- if dr:
1197
- degraded_reason_map[fid] = dr
1198
-
1199
- for feature in features:
1200
- if not isinstance(feature, dict):
1201
- continue
1202
- fid = feature.get("id")
1203
- title = feature.get("title", "Untitled")
1204
- if not fid:
1205
- continue
1206
-
1207
- fstatus = feature.get("status", "pending")
1208
- degraded_reason = degraded_reason_map.get(fid)
1209
-
1210
- # Count statuses
1211
- if fstatus in counts:
1212
- counts[fstatus] += 1
1213
- else:
1214
- counts["pending"] += 1
1215
-
1216
- # Build status indicator and color
1217
- # Show degraded reason via icon when a pending feature has one
1218
- if fstatus == "pending" and degraded_reason == "commit_missing":
1219
- icon = COLOR_RED + "[↑]" + COLOR_RESET
1220
- elif fstatus == "pending" and degraded_reason == "docs_missing":
1221
- icon = COLOR_RED + "[D]" + COLOR_RESET
1222
- elif fstatus == "pending" and degraded_reason == "merge_conflict":
1223
- icon = COLOR_RED + "[⚡]" + COLOR_RESET
1224
- elif fstatus == "completed":
1225
- icon = COLOR_GREEN + "[✓]" + COLOR_RESET
1226
- elif fstatus == "in_progress":
1227
- icon = COLOR_YELLOW + "[→]" + COLOR_RESET
1228
- elif fstatus == "failed":
1229
- icon = COLOR_RED + "[✗]" + COLOR_RESET
1230
- elif fstatus == "skipped":
1231
- icon = COLOR_GRAY + "[—]" + COLOR_RESET
1232
- elif fstatus == "auto_skipped":
1233
- icon = COLOR_GRAY + "[⊘]" + COLOR_RESET
1234
- else:
1235
- icon = COLOR_GRAY + "[ ]" + COLOR_RESET
1236
-
1237
- # Build detail suffix
1238
- detail = ""
1239
- if fstatus == "pending" and degraded_reason:
1240
- detail = " ({}, retrying)".format(degraded_reason)
1241
- # Also check if blocked by dependencies
1242
- deps = feature.get("dependencies", [])
1243
- blocking = [
1244
- d for d in deps
1245
- if status_map.get(d, "pending") != "completed"
1246
- ]
1247
- if blocking:
1248
- detail = " ({}, blocked by {})".format(degraded_reason, ", ".join(blocking))
1249
- elif fstatus == "pending":
1250
- # Check if blocked by dependencies
1251
- deps = feature.get("dependencies", [])
1252
- blocking = [
1253
- d for d in deps
1254
- if status_map.get(d, "pending") != "completed"
1255
- ]
1256
- if blocking:
1257
- detail = " (blocked by {})".format(", ".join(blocking))
1258
- elif fstatus == "auto_skipped":
1259
- deps = feature.get("dependencies", [])
1260
- blockers = [
1261
- d for d in deps
1262
- if status_map.get(d, "pending") in ("failed", "skipped", "auto_skipped")
1263
- ]
1264
- if blockers:
1265
- detail = " (auto-skipped: blocked by {})".format(", ".join(blockers))
1266
- elif fstatus == "failed" and degraded_reason:
1267
- detail = " (last failure: {})".format(degraded_reason)
1268
-
1269
- # Apply color to the whole line content
1270
- if fstatus == "completed":
1271
- line_content = "{} {} {}{}".format(
1272
- fid, icon, COLOR_GREEN + title + COLOR_RESET, detail
1273
- )
1274
- elif fstatus == "in_progress":
1275
- line_content = "{} {} {}{}".format(
1276
- fid, icon, COLOR_YELLOW + title + COLOR_RESET, detail
1277
- )
1278
- elif fstatus == "failed":
1279
- line_content = "{} {} {}{}".format(
1280
- fid, icon, COLOR_RED + title + COLOR_RESET, detail
1281
- )
1282
- elif degraded_reason:
1283
- line_content = "{} {} {}{}".format(
1284
- fid, icon, COLOR_RED + title + COLOR_RESET, detail
1285
- )
1286
- else:
1287
- line_content = "{} {} {}{}".format(
1288
- fid, icon, COLOR_GRAY + title + COLOR_RESET, detail
1289
- )
1290
-
1291
- feature_lines.append(line_content)
1292
-
1293
- total = len(features)
1294
- completed = counts["completed"]
1295
-
1296
- # Calculate percentage
1297
- if total > 0:
1298
- percent = round(completed / total * 100, 1)
1299
- else:
1300
- percent = 0.0
1301
-
1302
- # Generate progress bar
1303
- progress_bar = _build_progress_bar(percent, width=24)
1304
-
1305
- # Estimate remaining time
1306
- est_remaining, confidence = _estimate_remaining_time(
1307
- features, state_dir, counts, feature_list_data
1308
- )
1309
-
1310
- summary_line = "Total: {} features | Completed: {} | In Progress: {}".format(
1311
- total, completed, counts["in_progress"]
1312
- )
1313
- summary_line2 = "Failed: {} | Pending: {} | Skipped: {} | Auto-skipped: {}".format(
1314
- counts["failed"], counts["pending"], counts["skipped"], counts["auto_skipped"]
1315
- )
1316
-
1317
- # Count degraded features (pending with a degraded_reason from status.json)
1318
- degraded_count = sum(
1319
- 1 for fid, dr in degraded_reason_map.items()
1320
- if status_map.get(fid) == "pending" and dr
1321
- )
1322
- if degraded_count > 0:
1323
- summary_line3 = "Degraded (retrying): {}".format(degraded_count)
1324
- else:
1325
- summary_line3 = None
1326
-
1327
- # Build estimated remaining time line
1328
- CONFIDENCE_ICONS = {"high": "●", "medium": "◐", "low": "○"}
1329
- if est_remaining is not None:
1330
- eta_str = _format_duration(est_remaining)
1331
- conf_icon = CONFIDENCE_ICONS.get(confidence, "○")
1332
- eta_line = "ETA: ~{} (confidence: {} {})".format(
1333
- eta_str, conf_icon, confidence
1334
- )
1335
- else:
1336
- eta_line = "ETA: calculating... (need >=1 completed feature)"
1337
-
1338
- # Print the box
1339
- inner = BOX_WIDTH - 2 # space inside the vertical bars
1340
- print("╔" + "═" * BOX_WIDTH + "╗")
1341
- print("║" + pad_right(COLOR_BOLD + " Dev-Pipeline Status" + COLOR_RESET, inner) + " ║")
1342
- print("╠" + "═" * BOX_WIDTH + "╣")
1343
- print("║" + pad_right(" Project: {}".format(app_name), inner) + " ║")
1344
- print("║" + pad_right(" {}".format(summary_line), inner) + " ║")
1345
- print("║" + pad_right(" {}".format(summary_line2), inner) + " ║")
1346
- if summary_line3:
1347
- print("║" + pad_right(" {}".format(summary_line3), inner) + " ║")
1348
- print("╠" + "─" * BOX_WIDTH + "╣")
1349
- print("║" + pad_right(" Progress: {}".format(progress_bar), inner) + " ║")
1350
- print("║" + pad_right(" {}".format(eta_line), inner) + " ║")
1351
- print("╠" + "═" * BOX_WIDTH + "╣")
1352
- for line in feature_lines:
1353
- print("║" + pad_right(" {}".format(line), inner) + " ║")
1354
- print("╚" + "═" * BOX_WIDTH + "╝")
1355
-
1356
-
1357
- # ---------------------------------------------------------------------------
1358
- # Action: start
1359
- # ---------------------------------------------------------------------------
1360
-
1361
- def action_start(args, feature_list_path, state_dir):
1362
- """Mark a feature as in_progress at session start.
1363
-
1364
- This keeps .prizmkit/plans/feature-list.json/state status in sync during execution,
1365
- instead of only updating after session end.
1366
- """
1367
- feature_id = args.feature_id
1368
- if not feature_id:
1369
- error_out("--feature-id is required for 'start' action")
1370
- return
1371
-
1372
- fs = load_feature_status(state_dir, feature_id)
1373
- old_status = get_feature_status_from_list(feature_list_path, feature_id)
1374
-
1375
- fs["updated_at"] = now_iso()
1376
-
1377
- err = save_feature_status(state_dir, feature_id, fs)
1378
- if err:
1379
- error_out("Failed to save feature status: {}".format(err))
1380
- return
1381
-
1382
- if old_status != "in_progress":
1383
- err = update_feature_in_list(feature_list_path, feature_id, "in_progress")
1384
- if err:
1385
- error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
1386
- return
1387
-
1388
- result = {
1389
- "action": "start",
1390
- "feature_id": feature_id,
1391
- "old_status": old_status,
1392
- "new_status": "in_progress",
1393
- "retry_count": fs.get("retry_count", 0),
1394
- "infra_error_count": fs.get("infra_error_count", 0),
1395
- "updated_at": fs["updated_at"],
1396
- }
1397
- print(json.dumps(result, indent=2, ensure_ascii=False))
1398
-
1399
-
1400
- # ---------------------------------------------------------------------------
1401
- # Action: reset
1402
- # ---------------------------------------------------------------------------
1403
-
1404
- def action_reset(args, feature_list_path, state_dir):
1405
- """Reset a feature to pending state.
1406
-
1407
- Resets status.json runtime fields (retry_count -> 0, clear sessions,
1408
- clear resume_from_phase) and updates .prizmkit/plans/feature-list.json status to pending.
1409
- Does NOT delete any files on disk.
1410
- """
1411
- feature_id = args.feature_id
1412
- if not feature_id:
1413
- error_out("--feature-id is required for 'reset' action")
1414
- return
1415
-
1416
- # Load current status to preserve created_at
1417
- fs = load_feature_status(state_dir, feature_id)
1418
- old_status = get_feature_status_from_list(feature_list_path, feature_id)
1419
- old_retry = fs.get("retry_count", 0)
1420
-
1421
- # Reset runtime fields
1422
- fs["retry_count"] = 0
1423
- fs["infra_error_count"] = 0
1424
- fs["last_infra_error_session_id"] = None
1425
- fs["sessions"] = []
1426
- fs["last_session_id"] = None
1427
- fs["resume_from_phase"] = None
1428
- reset_continuation_metadata(fs)
1429
- fs["updated_at"] = now_iso()
1430
-
1431
- # Write back status.json
1432
- err = save_feature_status(state_dir, feature_id, fs)
1433
- if err:
1434
- error_out("Failed to save feature status: {}".format(err))
1435
- return
1436
-
1437
- # Update .prizmkit/plans/feature-list.json
1438
- err = update_feature_in_list(feature_list_path, feature_id, "pending")
1439
- if err:
1440
- error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
1441
- return
1442
-
1443
- result = {
1444
- "action": "reset",
1445
- "feature_id": feature_id,
1446
- "old_status": old_status,
1447
- "old_retry_count": old_retry,
1448
- "new_status": "pending",
1449
- }
1450
- print(json.dumps(result, indent=2, ensure_ascii=False))
1451
-
1452
-
1453
- # ---------------------------------------------------------------------------
1454
- # Action: clean
1455
- # ---------------------------------------------------------------------------
1456
-
1457
- def action_clean(args, feature_list_path, state_dir):
1458
- """Reset a feature AND delete all associated artifacts.
1459
-
1460
- Deletes:
1461
- - state/features/F-XXX/sessions/ (all session history)
1462
- - .prizmkit/specs/{slug}/ (spec, plan, tasks, contracts)
1463
-
1464
- Then performs a full reset (same as action_reset).
1465
- """
1466
- feature_id = args.feature_id
1467
- feature_slug = args.feature_slug
1468
- project_root = args.project_root
1469
-
1470
- if not feature_id:
1471
- error_out("--feature-id is required for 'clean' action")
1472
- return
1473
- if not feature_slug:
1474
- error_out("--feature-slug is required for 'clean' action")
1475
- return
1476
- if not project_root:
1477
- error_out("--project-root is required for 'clean' action")
1478
- return
1479
-
1480
- cleaned = []
1481
-
1482
- # 1. Delete session history
1483
- sessions_dir = os.path.join(state_dir, feature_id, "sessions")
1484
- sessions_deleted = 0
1485
- if os.path.isdir(sessions_dir):
1486
- for entry in os.listdir(sessions_dir):
1487
- entry_path = os.path.join(sessions_dir, entry)
1488
- if os.path.isdir(entry_path):
1489
- shutil.rmtree(entry_path)
1490
- sessions_deleted += 1
1491
- cleaned.append("Deleted {} session(s) from {}".format(
1492
- sessions_deleted, sessions_dir
1493
- ))
1494
-
1495
- # 2. Delete prizmkit specs for this feature
1496
- specs_dir = os.path.join(project_root, ".prizmkit", "specs", feature_slug)
1497
- if os.path.isdir(specs_dir):
1498
- file_count = sum(
1499
- len(files) for _, _, files in os.walk(specs_dir)
1500
- )
1501
- shutil.rmtree(specs_dir)
1502
- cleaned.append("Deleted {} ({} files)".format(specs_dir, file_count))
1503
-
1504
- # 3. Delete global dev-team workspace (shared AI transient context)
1505
- dev_team_dir = os.path.join(project_root, ".dev-team")
1506
- if os.path.isdir(dev_team_dir):
1507
- file_count = sum(len(files) for _, _, files in os.walk(dev_team_dir))
1508
- shutil.rmtree(dev_team_dir)
1509
- cleaned.append("Deleted {} ({} files)".format(dev_team_dir, file_count))
1510
-
1511
- # 4. (removed: current-session.json no longer used)
1512
-
1513
- # 5. Reset status (reuse reset logic)
1514
- fs = load_feature_status(state_dir, feature_id)
1515
- old_status = get_feature_status_from_list(feature_list_path, feature_id)
1516
- old_retry = fs.get("retry_count", 0)
1517
-
1518
- continuation_candidates = [
1519
- os.path.join(".prizmkit", "specs", feature_slug, "continuation-summary.md"),
1520
- ]
1521
- cleaned.extend(cleanup_continuation_artifacts(project_root, fs, continuation_candidates))
1522
-
1523
- fs["retry_count"] = 0
1524
- fs["infra_error_count"] = 0
1525
- fs["last_infra_error_session_id"] = None
1526
- fs["sessions"] = []
1527
- fs["last_session_id"] = None
1528
- fs["resume_from_phase"] = None
1529
- reset_continuation_metadata(fs)
1530
- fs["updated_at"] = now_iso()
1531
-
1532
- err = save_feature_status(state_dir, feature_id, fs)
1533
- if err:
1534
- error_out("Failed to save feature status: {}".format(err))
1535
- return
1536
-
1537
- err = update_feature_in_list(feature_list_path, feature_id, "pending")
1538
- if err:
1539
- error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
1540
- return
1541
-
1542
- result = {
1543
- "action": "clean",
1544
- "feature_id": feature_id,
1545
- "feature_slug": feature_slug,
1546
- "old_status": old_status,
1547
- "old_retry_count": old_retry,
1548
- "new_status": "pending",
1549
- "sessions_deleted": sessions_deleted,
1550
- "cleaned": cleaned,
1551
- }
1552
- print(json.dumps(result, indent=2, ensure_ascii=False))
1553
-
1554
-
1555
- # ---------------------------------------------------------------------------
1556
- # Action: unskip
1557
- # ---------------------------------------------------------------------------
1558
-
1559
- def action_unskip(args, feature_list_path, state_dir):
1560
- """Recover auto-skipped features by resetting them and their failed upstream.
1561
-
1562
- Two modes:
1563
- - --feature-id F-032: Reset the specified failed/skipped feature + all auto_skipped
1564
- features whose dependency chain includes it.
1565
- - No --feature-id: Reset ALL failed, skipped, and auto_skipped features to pending.
1566
- """
1567
- feature_id = args.feature_id
1568
-
1569
- data, err = load_json_file(feature_list_path)
1570
- if err:
1571
- error_out("Cannot load feature list: {}".format(err))
1572
- return
1573
- features = data.get("features", [])
1574
-
1575
- to_reset = set()
1576
-
1577
- if feature_id:
1578
- # Find the target feature
1579
- target = None
1580
- for f in features:
1581
- if isinstance(f, dict) and f.get("id") == feature_id:
1582
- target = f
1583
- break
1584
- if not target:
1585
- error_out("Feature '{}' not found in .prizmkit/plans/feature-list.json".format(feature_id))
1586
- return
1587
- if target.get("status") not in ("failed", "skipped", "auto_skipped"):
1588
- error_out(
1589
- "Feature '{}' has status '{}', expected 'failed', 'skipped', or 'auto_skipped'".format(
1590
- feature_id, target.get("status", "unknown")
1591
- )
1592
- )
1593
- return
1594
-
1595
- # If target is failed or skipped, reset it and find all auto_skipped descendants
1596
- if target.get("status") in ("failed", "skipped"):
1597
- to_reset.add(feature_id)
1598
- # Find all auto_skipped features that depend (transitively) on this one
1599
- changed = True
1600
- while changed:
1601
- changed = False
1602
- for f in features:
1603
- if not isinstance(f, dict):
1604
- continue
1605
- fid = f.get("id")
1606
- if not fid or fid in to_reset:
1607
- continue
1608
- if f.get("status") != "auto_skipped":
1609
- continue
1610
- deps = f.get("dependencies", [])
1611
- if any(d in to_reset for d in deps):
1612
- to_reset.add(fid)
1613
- changed = True
1614
-
1615
- # If target is auto_skipped, reset it and its failed upstream + siblings
1616
- elif target.get("status") == "auto_skipped":
1617
- to_reset.add(feature_id)
1618
- # Transitively walk upstream to find ALL failed/auto_skipped ancestors
1619
- # (e.g., F-001 failed → F-002 auto_skipped → F-003 auto_skipped;
1620
- # unskip F-003 must also find and reset F-001)
1621
- upstream_changed = True
1622
- while upstream_changed:
1623
- upstream_changed = False
1624
- for f in features:
1625
- if not isinstance(f, dict):
1626
- continue
1627
- fid = f.get("id")
1628
- if not fid or fid not in to_reset:
1629
- continue
1630
- for dep_id in f.get("dependencies", []):
1631
- if dep_id in to_reset:
1632
- continue
1633
- for dep_f in features:
1634
- if isinstance(dep_f, dict) and dep_f.get("id") == dep_id:
1635
- if dep_f.get("status") in ("failed", "skipped", "auto_skipped"):
1636
- to_reset.add(dep_id)
1637
- upstream_changed = True
1638
- # Also reset downstream auto_skipped features blocked by the same upstreams
1639
- changed = True
1640
- while changed:
1641
- changed = False
1642
- for f in features:
1643
- if not isinstance(f, dict):
1644
- continue
1645
- fid = f.get("id")
1646
- if not fid or fid in to_reset:
1647
- continue
1648
- if f.get("status") != "auto_skipped":
1649
- continue
1650
- fdeps = f.get("dependencies", [])
1651
- if any(d in to_reset for d in fdeps):
1652
- to_reset.add(fid)
1653
- changed = True
1654
- else:
1655
- # No feature-id: reset ALL failed + skipped + auto_skipped
1656
- for f in features:
1657
- if isinstance(f, dict) and f.get("id"):
1658
- if f.get("status") in ("failed", "skipped", "auto_skipped"):
1659
- to_reset.add(f["id"])
1660
-
1661
- if not to_reset:
1662
- error_out("No features to unskip")
1663
- return
1664
-
1665
- # Reset all collected features in .prizmkit/plans/feature-list.json
1666
- reset_details = []
1667
- for f in features:
1668
- if isinstance(f, dict) and f.get("id") in to_reset:
1669
- old_status = f.get("status", "unknown")
1670
- f["status"] = "pending"
1671
- reset_details.append({
1672
- "feature_id": f["id"],
1673
- "title": f.get("title", ""),
1674
- "old_status": old_status,
1675
- })
1676
-
1677
- err = write_json_file(feature_list_path, data)
1678
- if err:
1679
- error_out("Failed to write .prizmkit/plans/feature-list.json: {}".format(err))
1680
- return
1681
-
1682
- # Reset runtime fields in status.json for each feature
1683
- for fid in to_reset:
1684
- fs = load_feature_status(state_dir, fid)
1685
- fs["retry_count"] = 0
1686
- fs["infra_error_count"] = 0
1687
- fs["last_infra_error_session_id"] = None
1688
- fs["sessions"] = []
1689
- fs["last_session_id"] = None
1690
- fs["resume_from_phase"] = None
1691
- reset_continuation_metadata(fs)
1692
- fs["updated_at"] = now_iso()
1693
- save_feature_status(state_dir, fid, fs)
1694
-
1695
- result = {
1696
- "action": "unskip",
1697
- "reset_count": len(to_reset),
1698
- "features": reset_details,
1699
- }
1700
- print(json.dumps(result, indent=2, ensure_ascii=False))
1701
-
1702
-
1703
- # ---------------------------------------------------------------------------
1704
- # Action: pause
1705
- # ---------------------------------------------------------------------------
1706
-
1707
- def action_pause(state_dir):
1708
- """Save current pipeline state for graceful shutdown."""
1709
- pipeline_path = os.path.join(state_dir, "pipeline.json")
1710
-
1711
- data, err = load_json_file(pipeline_path)
1712
- if err:
1713
- # If pipeline.json doesn't exist, create a minimal one
1714
- data = {
1715
- "status": "paused",
1716
- "paused_at": now_iso(),
1717
- }
1718
- else:
1719
- data["status"] = "paused"
1720
- data["paused_at"] = now_iso()
1721
-
1722
- err = write_json_file(pipeline_path, data)
1723
- if err:
1724
- error_out("Failed to write pipeline.json: {}".format(err))
1725
- return
1726
-
1727
- result = {
1728
- "action": "pause",
1729
- "status": "paused",
1730
- "paused_at": data["paused_at"],
1731
- }
1732
- print(json.dumps(result, indent=2, ensure_ascii=False))
1733
-
1734
-
1735
- # ---------------------------------------------------------------------------
1736
- # Main
1737
- # ---------------------------------------------------------------------------
1738
-
1739
- def main():
1740
- args = parse_args()
1741
-
1742
- # Validate action-specific requirements
1743
- if args.action == "update":
1744
- if not args.feature_id:
1745
- error_out("--feature-id is required for 'update' action")
1746
- if not args.session_status:
1747
- error_out("--session-status is required for 'update' action")
1748
- if args.action in ("start", "reset", "clean", "complete"):
1749
- if not args.feature_id:
1750
- error_out("--feature-id is required for '{}' action".format(args.action))
1751
- if args.action == "clean":
1752
- if not args.feature_slug:
1753
- error_out("--feature-slug is required for 'clean' action")
1754
- if not args.project_root:
1755
- error_out("--project-root is required for 'clean' action")
1756
-
1757
- # Load feature list
1758
- feature_list_data, err = load_json_file(args.feature_list)
1759
- if err:
1760
- error_out("Cannot load feature list: {}".format(err))
1761
-
1762
- # Parse feature filter (used by get_next and status)
1763
- feature_filter = parse_feature_filter(args.features)
1764
-
1765
- # Dispatch action
1766
- if args.action == "get_next":
1767
- action_get_next(feature_list_data, args.state_dir, feature_filter)
1768
- elif args.action == "start":
1769
- action_start(args, args.feature_list, args.state_dir)
1770
- elif args.action == "update":
1771
- action_update(args, args.feature_list, args.state_dir)
1772
- elif args.action == "status":
1773
- action_status(feature_list_data, args.state_dir, feature_filter)
1774
- elif args.action == "reset":
1775
- action_reset(args, args.feature_list, args.state_dir)
1776
- elif args.action == "clean":
1777
- action_clean(args, args.feature_list, args.state_dir)
1778
- elif args.action == "complete":
1779
- # Shortcut: 'complete' is equivalent to 'update --session-status success'
1780
- args.session_status = "success"
1781
- action_update(args, args.feature_list, args.state_dir)
1782
- elif args.action == "pause":
1783
- action_pause(args.state_dir)
1784
- elif args.action == "unskip":
1785
- action_unskip(args, args.feature_list, args.state_dir)
1786
-
1787
-
1788
- if __name__ == "__main__":
1789
- main()