prizmkit 1.1.99 → 1.1.101

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 (321) 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 +345 -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 +147 -0
  26. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +422 -0
  27. package/bundled/dev-pipeline/prizmkit_runtime/sessions.py +552 -0
  28. package/bundled/dev-pipeline/prizmkit_runtime/status.py +71 -0
  29. package/bundled/dev-pipeline/scripts/check-session-status.py +74 -1
  30. package/bundled/dev-pipeline/scripts/continuation.py +374 -0
  31. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +64 -52
  32. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +35 -25
  33. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +2 -2
  34. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +15 -20
  35. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +185 -16
  36. package/bundled/dev-pipeline/scripts/update-bug-status.py +214 -6
  37. package/bundled/dev-pipeline/scripts/update-feature-status.py +237 -6
  38. package/bundled/dev-pipeline/scripts/update-refactor-status.py +214 -6
  39. package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +8 -2
  40. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +4 -0
  41. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +14 -5
  42. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +16 -6
  43. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +16 -6
  44. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +1 -1
  45. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +3 -3
  46. package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +2 -0
  47. package/bundled/dev-pipeline/templates/sections/log-size-awareness.md +13 -69
  48. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +15 -7
  49. package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +42 -29
  50. package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +41 -39
  51. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-agent.md +4 -0
  52. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-lite.md +4 -0
  53. package/bundled/dev-pipeline/templates/session-status-schema.json +1 -1
  54. package/bundled/dev-pipeline/tests/conftest.py +1 -0
  55. package/bundled/dev-pipeline/tests/test_auto_skip.py +932 -0
  56. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +207 -0
  57. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +100 -0
  58. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +43 -0
  59. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +1138 -0
  60. package/bundled/dev-pipeline/tests/test_unified_cli.py +1045 -0
  61. package/bundled/skills/_metadata.json +1 -1
  62. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +29 -34
  63. package/bundled/skills/bugfix-pipeline-launcher/references/configuration.md +6 -5
  64. package/bundled/skills/feature-pipeline-launcher/SKILL.md +29 -35
  65. package/bundled/skills/feature-pipeline-launcher/references/configuration.md +7 -6
  66. package/bundled/skills/feature-workflow/SKILL.md +3 -6
  67. package/bundled/skills/recovery-workflow/SKILL.md +23 -32
  68. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +29 -35
  69. package/bundled/skills/refactor-pipeline-launcher/references/configuration.md +7 -6
  70. package/bundled/skills/refactor-workflow/SKILL.md +3 -6
  71. package/bundled/templates/hooks/commit-intent.json +2 -2
  72. package/bundled/templates/hooks/diff-prizm-docs.py +226 -0
  73. package/bundled/templates/hooks/prizm-pre-commit.py +45 -0
  74. package/bundled/templates/hooks/validate-prizm-docs.py +240 -0
  75. package/package.json +1 -1
  76. package/src/config.js +33 -12
  77. package/src/index.js +3 -6
  78. package/src/manifest.js +3 -2
  79. package/src/metadata.js +7 -18
  80. package/src/prompts.js +0 -12
  81. package/src/runtimes.js +18 -11
  82. package/src/scaffold.js +133 -116
  83. package/src/upgrade.js +1 -1
  84. package/bundled/dev-pipeline/launch-bugfix-daemon.sh +0 -500
  85. package/bundled/dev-pipeline/launch-feature-daemon.sh +0 -657
  86. package/bundled/dev-pipeline/launch-refactor-daemon.sh +0 -502
  87. package/bundled/dev-pipeline/lib/branch.sh +0 -326
  88. package/bundled/dev-pipeline/lib/common.sh +0 -1257
  89. package/bundled/dev-pipeline/lib/heartbeat.sh +0 -469
  90. package/bundled/dev-pipeline/reset-bug.sh +0 -395
  91. package/bundled/dev-pipeline/reset-feature.sh +0 -406
  92. package/bundled/dev-pipeline/reset-refactor.sh +0 -393
  93. package/bundled/dev-pipeline/run-bugfix.sh +0 -1315
  94. package/bundled/dev-pipeline/run-feature.sh +0 -1602
  95. package/bundled/dev-pipeline/run-recovery.sh +0 -648
  96. package/bundled/dev-pipeline/run-refactor.sh +0 -1348
  97. package/bundled/dev-pipeline/scripts/monitor-log.sh +0 -104
  98. package/bundled/dev-pipeline/tests/test-path-resolution.sh +0 -100
  99. package/bundled/dev-pipeline-windows/.env.example +0 -36
  100. package/bundled/dev-pipeline-windows/README.md +0 -30
  101. package/bundled/dev-pipeline-windows/SCHEMA_ANALYSIS.md +0 -533
  102. package/bundled/dev-pipeline-windows/assets/feature-list-example.json +0 -146
  103. package/bundled/dev-pipeline-windows/assets/prizm-dev-team-integration.md +0 -137
  104. package/bundled/dev-pipeline-windows/launch-bugfix-daemon.ps1 +0 -9
  105. package/bundled/dev-pipeline-windows/launch-feature-daemon.ps1 +0 -9
  106. package/bundled/dev-pipeline-windows/launch-refactor-daemon.ps1 +0 -9
  107. package/bundled/dev-pipeline-windows/lib/branch.ps1 +0 -235
  108. package/bundled/dev-pipeline-windows/lib/common.ps1 +0 -829
  109. package/bundled/dev-pipeline-windows/lib/daemon.ps1 +0 -140
  110. package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +0 -967
  111. package/bundled/dev-pipeline-windows/lib/reset.ps1 +0 -87
  112. package/bundled/dev-pipeline-windows/reset-bug.ps1 +0 -9
  113. package/bundled/dev-pipeline-windows/reset-feature.ps1 +0 -9
  114. package/bundled/dev-pipeline-windows/reset-refactor.ps1 +0 -9
  115. package/bundled/dev-pipeline-windows/run-bugfix.ps1 +0 -9
  116. package/bundled/dev-pipeline-windows/run-feature.ps1 +0 -9
  117. package/bundled/dev-pipeline-windows/run-recovery.ps1 +0 -179
  118. package/bundled/dev-pipeline-windows/run-refactor.ps1 +0 -9
  119. package/bundled/dev-pipeline-windows/scripts/check-session-status.py +0 -228
  120. package/bundled/dev-pipeline-windows/scripts/detect-stuck.py +0 -530
  121. package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +0 -2282
  122. package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +0 -767
  123. package/bundled/dev-pipeline-windows/scripts/generate-recovery-prompt.py +0 -805
  124. package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +0 -845
  125. package/bundled/dev-pipeline-windows/scripts/init-bugfix-pipeline.py +0 -316
  126. package/bundled/dev-pipeline-windows/scripts/init-dev-team.py +0 -134
  127. package/bundled/dev-pipeline-windows/scripts/init-pipeline.py +0 -382
  128. package/bundled/dev-pipeline-windows/scripts/init-refactor-pipeline.py +0 -399
  129. package/bundled/dev-pipeline-windows/scripts/monitor-log.ps1 +0 -102
  130. package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +0 -1232
  131. package/bundled/dev-pipeline-windows/scripts/patch-completion-notes.py +0 -191
  132. package/bundled/dev-pipeline-windows/scripts/prizmkit-test-gate.py +0 -446
  133. package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +0 -913
  134. package/bundled/dev-pipeline-windows/scripts/update-checkpoint.py +0 -173
  135. package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +0 -1558
  136. package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +0 -1119
  137. package/bundled/dev-pipeline-windows/scripts/utils.py +0 -542
  138. package/bundled/dev-pipeline-windows/templates/agent-prompts/critic-plan-challenge.md +0 -7
  139. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-fix.md +0 -7
  140. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-implement.md +0 -65
  141. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-resume.md +0 -5
  142. package/bundled/dev-pipeline-windows/templates/agent-prompts/reviewer-review.md +0 -7
  143. package/bundled/dev-pipeline-windows/templates/bootstrap-prompt.md +0 -73
  144. package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +0 -589
  145. package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +0 -710
  146. package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +0 -791
  147. package/bundled/dev-pipeline-windows/templates/bug-fix-list-schema.json +0 -263
  148. package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +0 -326
  149. package/bundled/dev-pipeline-windows/templates/feature-list-schema.json +0 -237
  150. package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +0 -340
  151. package/bundled/dev-pipeline-windows/templates/refactor-list-schema.json +0 -270
  152. package/bundled/dev-pipeline-windows/templates/sections/ac-verification-checklist.md +0 -7
  153. package/bundled/dev-pipeline-windows/templates/sections/checkpoint-system.md +0 -91
  154. package/bundled/dev-pipeline-windows/templates/sections/context-budget-rules.md +0 -34
  155. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-agent.md +0 -10
  156. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-full.md +0 -12
  157. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-lite.md +0 -7
  158. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-agent.md +0 -8
  159. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-full.md +0 -9
  160. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-lite.md +0 -6
  161. package/bundled/dev-pipeline-windows/templates/sections/failure-capture.md +0 -21
  162. package/bundled/dev-pipeline-windows/templates/sections/feature-context.md +0 -21
  163. package/bundled/dev-pipeline-windows/templates/sections/log-size-awareness.md +0 -78
  164. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-auto.md +0 -283
  165. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-opencli.md +0 -112
  166. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification.md +0 -168
  167. package/bundled/dev-pipeline-windows/templates/sections/phase-commit-full.md +0 -82
  168. package/bundled/dev-pipeline-windows/templates/sections/phase-commit.md +0 -75
  169. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-agent-suffix.md +0 -23
  170. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-base.md +0 -14
  171. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-lite-suffix.md +0 -19
  172. package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan-full.md +0 -63
  173. package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan.md +0 -42
  174. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-agent.md +0 -44
  175. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-full.md +0 -59
  176. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-lite.md +0 -39
  177. package/bundled/dev-pipeline-windows/templates/sections/phase-plan-agent.md +0 -27
  178. package/bundled/dev-pipeline-windows/templates/sections/phase-plan-lite.md +0 -27
  179. package/bundled/dev-pipeline-windows/templates/sections/phase-prizmkit-test.md +0 -39
  180. package/bundled/dev-pipeline-windows/templates/sections/phase-review-agent.md +0 -35
  181. package/bundled/dev-pipeline-windows/templates/sections/phase-review-full.md +0 -35
  182. package/bundled/dev-pipeline-windows/templates/sections/phase-specify-plan-full.md +0 -73
  183. package/bundled/dev-pipeline-windows/templates/sections/phase0-init.md +0 -13
  184. package/bundled/dev-pipeline-windows/templates/sections/phase0-test-baseline.md +0 -21
  185. package/bundled/dev-pipeline-windows/templates/sections/session-context.md +0 -5
  186. package/bundled/dev-pipeline-windows/templates/sections/subagent-timeout-recovery.md +0 -6
  187. package/bundled/dev-pipeline-windows/templates/sections/task-contract.md +0 -34
  188. package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-agent.md +0 -48
  189. package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-lite.md +0 -48
  190. package/bundled/dev-pipeline-windows/templates/session-status-schema.json +0 -83
  191. package/bundled/skills-windows/app-planner/SKILL.md +0 -296
  192. package/bundled/skills-windows/app-planner/assets/app-design-guide.md +0 -101
  193. package/bundled/skills-windows/app-planner/references/architecture-decisions.md +0 -52
  194. package/bundled/skills-windows/app-planner/references/brainstorm-guide.md +0 -101
  195. package/bundled/skills-windows/app-planner/references/frontend-design-guide.md +0 -71
  196. package/bundled/skills-windows/app-planner/references/infrastructure-convention-discovery.md +0 -108
  197. package/bundled/skills-windows/app-planner/references/project-brief-guide.md +0 -82
  198. package/bundled/skills-windows/app-planner/references/project-conventions-discovery.md +0 -59
  199. package/bundled/skills-windows/app-planner/references/project-state-detection.md +0 -90
  200. package/bundled/skills-windows/app-planner/references/red-team-checklist.md +0 -40
  201. package/bundled/skills-windows/app-planner/references/rules/backend/derivation-rules.md +0 -619
  202. package/bundled/skills-windows/app-planner/references/rules/backend/fixed-rules.md +0 -285
  203. package/bundled/skills-windows/app-planner/references/rules/backend/question-bank.md +0 -249
  204. package/bundled/skills-windows/app-planner/references/rules/backend/question-manifest.json +0 -46
  205. package/bundled/skills-windows/app-planner/references/rules/backend/template.md +0 -173
  206. package/bundled/skills-windows/app-planner/references/rules/database/derivation-rules.md +0 -382
  207. package/bundled/skills-windows/app-planner/references/rules/database/fixed-rules.md +0 -211
  208. package/bundled/skills-windows/app-planner/references/rules/database/question-bank.md +0 -184
  209. package/bundled/skills-windows/app-planner/references/rules/database/question-manifest.json +0 -39
  210. package/bundled/skills-windows/app-planner/references/rules/database/template.md +0 -158
  211. package/bundled/skills-windows/app-planner/references/rules/frontend/derivation-rules.md +0 -820
  212. package/bundled/skills-windows/app-planner/references/rules/frontend/fixed-rules.md +0 -188
  213. package/bundled/skills-windows/app-planner/references/rules/frontend/question-bank.md +0 -319
  214. package/bundled/skills-windows/app-planner/references/rules/frontend/question-manifest.json +0 -51
  215. package/bundled/skills-windows/app-planner/references/rules/frontend/template.md +0 -339
  216. package/bundled/skills-windows/app-planner/references/rules/mobile/derivation-rules.md +0 -649
  217. package/bundled/skills-windows/app-planner/references/rules/mobile/fixed-rules.md +0 -290
  218. package/bundled/skills-windows/app-planner/references/rules/mobile/question-bank.md +0 -232
  219. package/bundled/skills-windows/app-planner/references/rules/mobile/question-manifest.json +0 -47
  220. package/bundled/skills-windows/app-planner/references/rules/mobile/template.md +0 -175
  221. package/bundled/skills-windows/app-planner/references/rules-configuration.md +0 -46
  222. package/bundled/skills-windows/bug-fix-workflow/SKILL.md +0 -379
  223. package/bundled/skills-windows/bug-fix-workflow/references/bug-diagnosis.md +0 -41
  224. package/bundled/skills-windows/bug-planner/SKILL.md +0 -402
  225. package/bundled/skills-windows/bug-planner/assets/bug-confirmation-template.md +0 -43
  226. package/bundled/skills-windows/bug-planner/references/critic-and-verification.md +0 -44
  227. package/bundled/skills-windows/bug-planner/references/error-recovery.md +0 -73
  228. package/bundled/skills-windows/bug-planner/references/input-formats.md +0 -53
  229. package/bundled/skills-windows/bug-planner/references/schema-validation.md +0 -25
  230. package/bundled/skills-windows/bug-planner/references/severity-rules.md +0 -16
  231. package/bundled/skills-windows/bug-planner/scripts/validate-bug-list.py +0 -322
  232. package/bundled/skills-windows/bugfix-pipeline-launcher/SKILL.md +0 -304
  233. package/bundled/skills-windows/bugfix-pipeline-launcher/references/configuration.md +0 -84
  234. package/bundled/skills-windows/feature-pipeline-launcher/SKILL.md +0 -381
  235. package/bundled/skills-windows/feature-pipeline-launcher/references/configuration.md +0 -67
  236. package/bundled/skills-windows/feature-pipeline-launcher/scripts/preflight-check.py +0 -462
  237. package/bundled/skills-windows/feature-planner/SKILL.md +0 -397
  238. package/bundled/skills-windows/feature-planner/assets/evaluation-guide.md +0 -64
  239. package/bundled/skills-windows/feature-planner/assets/planning-guide.md +0 -214
  240. package/bundled/skills-windows/feature-planner/references/browser-interaction.md +0 -59
  241. package/bundled/skills-windows/feature-planner/references/completeness-review.md +0 -57
  242. package/bundled/skills-windows/feature-planner/references/decomposition-patterns.md +0 -75
  243. package/bundled/skills-windows/feature-planner/references/error-recovery.md +0 -90
  244. package/bundled/skills-windows/feature-planner/references/incremental-feature-planning.md +0 -112
  245. package/bundled/skills-windows/feature-planner/references/new-project-planning.md +0 -85
  246. package/bundled/skills-windows/feature-planner/scripts/validate-and-generate.py +0 -1020
  247. package/bundled/skills-windows/feature-workflow/SKILL.md +0 -374
  248. package/bundled/skills-windows/feature-workflow/references/brainstorm-guide.md +0 -137
  249. package/bundled/skills-windows/prizm-kit/SKILL.md +0 -93
  250. package/bundled/skills-windows/prizmkit-code-review/SKILL.md +0 -161
  251. package/bundled/skills-windows/prizmkit-code-review/references/dev-agent-prompt.md +0 -30
  252. package/bundled/skills-windows/prizmkit-code-review/references/review-report-template.md +0 -31
  253. package/bundled/skills-windows/prizmkit-code-review/references/reviewer-agent-prompt.md +0 -66
  254. package/bundled/skills-windows/prizmkit-code-review/scripts/check_loop.py +0 -186
  255. package/bundled/skills-windows/prizmkit-committer/SKILL.md +0 -89
  256. package/bundled/skills-windows/prizmkit-deploy/SKILL.md +0 -444
  257. package/bundled/skills-windows/prizmkit-deploy/references/ci-cd-workflows.md +0 -115
  258. package/bundled/skills-windows/prizmkit-deploy/references/cloud-platform-deploy.md +0 -93
  259. package/bundled/skills-windows/prizmkit-deploy/references/data-safety-examples.md +0 -120
  260. package/bundled/skills-windows/prizmkit-deploy/references/database-setup.md +0 -46
  261. package/bundled/skills-windows/prizmkit-deploy/references/deploy-config-schema.md +0 -148
  262. package/bundled/skills-windows/prizmkit-deploy/references/deploy-history-schema.md +0 -62
  263. package/bundled/skills-windows/prizmkit-deploy/references/deployment-modes.md +0 -50
  264. package/bundled/skills-windows/prizmkit-deploy/references/direct-upload.md +0 -26
  265. package/bundled/skills-windows/prizmkit-deploy/references/dns-setup.md +0 -42
  266. package/bundled/skills-windows/prizmkit-deploy/references/docker-deploy.md +0 -31
  267. package/bundled/skills-windows/prizmkit-deploy/references/firewall-setup.md +0 -37
  268. package/bundled/skills-windows/prizmkit-deploy/references/live-validation-notes.md +0 -21
  269. package/bundled/skills-windows/prizmkit-deploy/references/nginx-blue-green.md +0 -59
  270. package/bundled/skills-windows/prizmkit-deploy/references/ssh-bootstrap-flow.md +0 -49
  271. package/bundled/skills-windows/prizmkit-deploy/references/ssh-execution-flow.md +0 -41
  272. package/bundled/skills-windows/prizmkit-deploy/references/ssh-takeover.md +0 -20
  273. package/bundled/skills-windows/prizmkit-deploy/references/ssl-setup.md +0 -56
  274. package/bundled/skills-windows/prizmkit-implement/SKILL.md +0 -71
  275. package/bundled/skills-windows/prizmkit-init/SKILL.md +0 -356
  276. package/bundled/skills-windows/prizmkit-init/assets/project-brief-template.md +0 -82
  277. package/bundled/skills-windows/prizmkit-init/references/config-schema.md +0 -70
  278. package/bundled/skills-windows/prizmkit-init/references/rules/layer-detection.md +0 -41
  279. package/bundled/skills-windows/prizmkit-init/references/tech-stack-catalog.md +0 -13
  280. package/bundled/skills-windows/prizmkit-init/references/update-supplement.md +0 -9
  281. package/bundled/skills-windows/prizmkit-plan/SKILL.md +0 -102
  282. package/bundled/skills-windows/prizmkit-plan/assets/plan-template.md +0 -115
  283. package/bundled/skills-windows/prizmkit-plan/assets/spec-template.md +0 -73
  284. package/bundled/skills-windows/prizmkit-plan/references/clarify-guide.md +0 -67
  285. package/bundled/skills-windows/prizmkit-plan/references/examples.md +0 -85
  286. package/bundled/skills-windows/prizmkit-plan/references/verification-checklist.md +0 -60
  287. package/bundled/skills-windows/prizmkit-prizm-docs/SKILL.md +0 -129
  288. package/bundled/skills-windows/prizmkit-prizm-docs/assets/prizm-docs-format.md +0 -613
  289. package/bundled/skills-windows/prizmkit-prizm-docs/references/op-init.md +0 -45
  290. package/bundled/skills-windows/prizmkit-prizm-docs/references/op-rebuild.md +0 -15
  291. package/bundled/skills-windows/prizmkit-prizm-docs/references/op-status.md +0 -14
  292. package/bundled/skills-windows/prizmkit-prizm-docs/references/op-update.md +0 -19
  293. package/bundled/skills-windows/prizmkit-prizm-docs/references/op-validate.md +0 -17
  294. package/bundled/skills-windows/prizmkit-retrospective/SKILL.md +0 -87
  295. package/bundled/skills-windows/prizmkit-retrospective/references/knowledge-injection-steps.md +0 -50
  296. package/bundled/skills-windows/prizmkit-retrospective/references/structural-sync-steps.md +0 -43
  297. package/bundled/skills-windows/prizmkit-test/SKILL.md +0 -227
  298. package/bundled/skills-windows/prizmkit-test/references/boundary-coverage-protocol.md +0 -220
  299. package/bundled/skills-windows/prizmkit-test/references/examples.md +0 -143
  300. package/bundled/skills-windows/prizmkit-test/references/service-boundary-test-catalog.md +0 -225
  301. package/bundled/skills-windows/prizmkit-test/references/test-generation-steps.md +0 -109
  302. package/bundled/skills-windows/prizmkit-test/references/test-report-template.md +0 -111
  303. package/bundled/skills-windows/prizmkit-test/scripts/validate_boundary_report.py +0 -347
  304. package/bundled/skills-windows/recovery-workflow/SKILL.md +0 -355
  305. package/bundled/skills-windows/recovery-workflow/evals/evals.json +0 -46
  306. package/bundled/skills-windows/recovery-workflow/references/detection.md +0 -58
  307. package/bundled/skills-windows/recovery-workflow/scripts/detect-recovery-state.py +0 -544
  308. package/bundled/skills-windows/refactor-pipeline-launcher/SKILL.md +0 -346
  309. package/bundled/skills-windows/refactor-pipeline-launcher/references/configuration.md +0 -72
  310. package/bundled/skills-windows/refactor-planner/SKILL.md +0 -398
  311. package/bundled/skills-windows/refactor-planner/assets/planning-guide.md +0 -292
  312. package/bundled/skills-windows/refactor-planner/references/behavior-preservation.md +0 -301
  313. package/bundled/skills-windows/refactor-planner/references/fast-path.md +0 -59
  314. package/bundled/skills-windows/refactor-planner/references/planning-phases.md +0 -135
  315. package/bundled/skills-windows/refactor-planner/references/refactor-scoping-guide.md +0 -221
  316. package/bundled/skills-windows/refactor-planner/scripts/validate-and-generate-refactor.py +0 -858
  317. package/bundled/skills-windows/refactor-workflow/SKILL.md +0 -343
  318. package/bundled/skills-windows/refactor-workflow/references/brainstorm-guide.md +0 -116
  319. package/bundled/templates/hooks/diff-prizm-docs.sh +0 -158
  320. package/bundled/templates/hooks/prizm-pre-commit.sh +0 -108
  321. package/bundled/templates/hooks/validate-prizm-docs.sh +0 -139
@@ -0,0 +1,1138 @@
1
+ """Parity-focused tests for Python foreground runner helpers."""
2
+
3
+ import json
4
+ import os
5
+ import shutil
6
+ import subprocess
7
+ import sys
8
+ from pathlib import Path
9
+ from types import SimpleNamespace
10
+
11
+ import pytest
12
+
13
+ REPO_ROOT = Path(__file__).resolve().parents[2]
14
+ PIPELINE_ROOT = REPO_ROOT / "dev-pipeline"
15
+ CLI_PATH = PIPELINE_ROOT / "cli.py"
16
+
17
+ if str(PIPELINE_ROOT) not in sys.path:
18
+ sys.path.insert(0, str(PIPELINE_ROOT))
19
+
20
+
21
+ def _python_310_plus() -> str:
22
+ if sys.version_info >= (3, 10):
23
+ return sys.executable
24
+ for executable in ("python3.12", "python3.11", "python3.10"):
25
+ resolved = shutil.which(executable)
26
+ if resolved:
27
+ return resolved
28
+ return sys.executable
29
+
30
+
31
+ PYTHON_310_PLUS = _python_310_plus()
32
+ UNIX_RUNTIME_SNAPSHOT = REPO_ROOT / "tests" / "fixtures" / "dev-pipeline-behavior-snapshots" / "unix-runtime.json"
33
+
34
+
35
+ def _load_unix_runtime_snapshot():
36
+ return json.loads(UNIX_RUNTIME_SNAPSHOT.read_text(encoding="utf-8"))
37
+
38
+
39
+ def _make_paths(tmp_path):
40
+ from prizmkit_runtime.paths import resolve_runtime_paths
41
+
42
+ project = tmp_path / "project"
43
+ (project / ".prizmkit" / "plans").mkdir(parents=True)
44
+ return resolve_runtime_paths(pipeline_root=PIPELINE_ROOT, project_root=project)
45
+
46
+
47
+ def _write_feature_list(paths):
48
+ paths.feature_plan.write_text(
49
+ json.dumps(
50
+ {
51
+ "$schema": "dev-pipeline-feature-list-v1",
52
+ "project_name": "Fake",
53
+ "features": [
54
+ {"id": "F-001", "title": "Low", "status": "pending", "priority": "low", "dependencies": []},
55
+ {"id": "F-002", "title": "Critical", "status": "pending", "priority": "critical", "dependencies": []},
56
+ {"id": "F-003", "title": "Blocked", "status": "pending", "priority": "high", "dependencies": ["F-001"]},
57
+ ],
58
+ }
59
+ ),
60
+ encoding="utf-8",
61
+ )
62
+
63
+
64
+ def test_retired_shell_runtime_files_are_absent_from_source():
65
+ retired = [
66
+ "run-feature.sh",
67
+ "run-bugfix.sh",
68
+ "run-refactor.sh",
69
+ "run-recovery.sh",
70
+ "launch-feature-daemon.sh",
71
+ "launch-bugfix-daemon.sh",
72
+ "launch-refactor-daemon.sh",
73
+ "reset-feature.sh",
74
+ "reset-bug.sh",
75
+ "reset-refactor.sh",
76
+ "lib/branch.sh",
77
+ "lib/common.sh",
78
+ "lib/heartbeat.sh",
79
+ ]
80
+
81
+ for relative_path in retired:
82
+ assert not (PIPELINE_ROOT / relative_path).exists(), relative_path
83
+
84
+
85
+ def test_python_parity_suite_declares_coverage_for_all_legacy_unix_snapshot_scenarios():
86
+ """Keep the F-011 snapshot as an explicit oracle without reviving shell sources."""
87
+ snapshot = _load_unix_runtime_snapshot()
88
+ suite_text = "\n".join(
89
+ [
90
+ Path(__file__).read_text(encoding="utf-8"),
91
+ (PIPELINE_ROOT / "tests" / "test_unified_cli.py").read_text(encoding="utf-8"),
92
+ (PIPELINE_ROOT / "tests" / "test_auto_skip.py").read_text(encoding="utf-8"),
93
+ ]
94
+ )
95
+ coverage_tokens = {
96
+ "entrypointContracts": {
97
+ "runnerHelpContracts": ["test_cli_help_exposes_python_runtime_command_groups"],
98
+ "runnerMissingListErrors": ["test_python_runner_missing_list_errors_match_snapshot_contract"],
99
+ "recoveryHelpContract": ["test_python_entrypoint_contracts_match_snapshot_contracts"],
100
+ "resetHelpContracts": ["test_python_entrypoint_contracts_match_snapshot_contracts"],
101
+ "daemonStatusContracts": ["test_python_entrypoint_contracts_match_snapshot_contracts"],
102
+ },
103
+ "envResolution": {
104
+ "verboseAndStreamJson": ["test_python_env_resolution_scenarios_match_snapshot_contracts"],
105
+ "effortModelAndCli": ["test_ai_session_command_builders_match_unix_runtime_snapshot"],
106
+ "worktreeIgnored": ["test_runner_environment_enables_worktree_by_default_and_accepts_falsey_opt_out"],
107
+ "autoPushFlag": ["test_successful_worktree_session_merges_cleans_branch_and_honors_auto_push"],
108
+ },
109
+ "aiCliCommands": {
110
+ "claudeCommand": ["test_ai_session_command_builders_match_unix_runtime_snapshot"],
111
+ "codexCommand": ["test_ai_session_command_builders_match_unix_runtime_snapshot"],
112
+ "customWrapperCommand": ["test_ai_session_command_builders_match_unix_runtime_snapshot"],
113
+ },
114
+ "fakeCliOutcomes": {
115
+ "success": ["test_session_dual_writes_progress_and_recovers_backup"],
116
+ "contextOverflowContinuation": ["test_context_overflow_reuses_worktree_and_skips_cleanup_until_terminal_outcome"],
117
+ "infraErrorRetry": ["test_status_updater_behavior_matches_unix_runtime_snapshot"],
118
+ "crashFailure": ["test_status_updater_behavior_matches_unix_runtime_snapshot"],
119
+ "timeoutStaleKill": ["test_heartbeat_fatal_error_writes_shell_compatible_markers"],
120
+ },
121
+ "artifactShapes": {
122
+ "sessionLogsAndProgress": ["test_session_dual_writes_progress_and_recovers_backup"],
123
+ "homeBackupNormalization": ["test_session_dual_writes_progress_and_recovers_backup"],
124
+ },
125
+ "resetBehavior": {
126
+ "manualResetWarningOrder": ["test_python_reset_warns_before_mutation_and_matches_snapshot_cleanup"],
127
+ "cleanResetDeletesContinuationArtifacts": ["test_python_clean_reset_deletes_continuation_artifacts_for_all_families"],
128
+ },
129
+ "daemonBehavior": {
130
+ "stalePidStatusByTaskType": ["test_python_daemon_stale_pid_matches_snapshot"],
131
+ "controlledStartStop": ["test_python_daemon_controlled_start_status_stop_matches_snapshot"],
132
+ },
133
+ "continuationBehavior": {
134
+ "noProgressStall": ["test_status_updater_behavior_matches_unix_runtime_snapshot"],
135
+ "branchAndArtifactReuse": ["test_context_overflow_reuses_worktree_and_skips_cleanup_until_terminal_outcome"],
136
+ },
137
+ "statusUpdaterBehavior": {
138
+ "contextOverflowMetadata": ["test_status_updater_behavior_matches_unix_runtime_snapshot"],
139
+ "infraErrorRetryBudget": ["test_status_updater_behavior_matches_unix_runtime_snapshot"],
140
+ "crashFailureBudget": ["test_status_updater_behavior_matches_unix_runtime_snapshot"],
141
+ "resetCleanMetadataCleanup": ["test_status_updater_behavior_matches_unix_runtime_snapshot"],
142
+ "noProgressStall": ["test_status_updater_behavior_matches_unix_runtime_snapshot"],
143
+ },
144
+ }
145
+
146
+ assert set(coverage_tokens) == set(snapshot["requiredScenarios"])
147
+ for section, scenario_names in snapshot["requiredScenarios"].items():
148
+ assert set(coverage_tokens[section]) == set(scenario_names), section
149
+ for scenario_name, tokens in coverage_tokens[section].items():
150
+ assert tokens, f"{section}.{scenario_name} has no coverage tokens"
151
+ for token in tokens:
152
+ assert token in suite_text, f"{section}.{scenario_name} coverage token missing: {token}"
153
+
154
+
155
+ def test_python_runner_missing_list_errors_match_snapshot_contract(tmp_path):
156
+ from prizmkit_runtime.runners import run_pipeline_command
157
+
158
+ snapshot = _load_unix_runtime_snapshot()["entrypointContracts"]["runnerMissingListErrors"]
159
+ for kind in ("feature", "bugfix", "refactor"):
160
+ paths = _make_paths(tmp_path / f"missing-{kind}")
161
+ result = run_pipeline_command(kind, "run", (), paths)
162
+
163
+ assert result.exit_code == snapshot[kind]["exitCode"]
164
+ assert "Plan list not found" in result.render()
165
+
166
+
167
+ def test_python_entrypoint_contracts_match_snapshot_contracts(tmp_path):
168
+ from prizmkit_runtime.commands import handle_runtime_command
169
+ from prizmkit_runtime.daemon import run_daemon_command
170
+ from prizmkit_runtime.paths import resolve_runtime_paths
171
+ from prizmkit_runtime.reset import run_reset_command
172
+
173
+ snapshot = _load_unix_runtime_snapshot()["entrypointContracts"]
174
+ paths = _make_paths(tmp_path / "entrypoints")
175
+
176
+ recovery = handle_runtime_command("recovery", "describe", paths=paths)
177
+ assert recovery.exit_code == snapshot["recoveryHelpContract"]["exitCode"]
178
+ assert "Python runtime command contract" in recovery.render()
179
+ assert "recovery describe" in recovery.render()
180
+
181
+ for kind in ("feature", "bugfix", "refactor"):
182
+ reset = run_reset_command(kind, ("--help",), paths)
183
+ assert reset.exit_code == snapshot["resetHelpContracts"][kind]["exitCode"]
184
+ assert "Usage: prizmkit-runtime reset" in reset.render()
185
+
186
+ daemon_paths = resolve_runtime_paths(
187
+ pipeline_root=PIPELINE_ROOT,
188
+ project_root=tmp_path / "daemon-status" / kind,
189
+ )
190
+ daemon = run_daemon_command(kind, "status", (), daemon_paths)
191
+ payload = json.loads(daemon.render())
192
+ assert daemon.exit_code == snapshot["daemonStatusContracts"][kind]["exitCode"]
193
+ assert payload["running"] is False
194
+
195
+
196
+ def test_python_env_resolution_scenarios_match_snapshot_contracts(tmp_path):
197
+ from prizmkit_runtime.sessions import AISessionConfig, build_ai_session_command, detect_stream_json_support
198
+
199
+ snapshot = _load_unix_runtime_snapshot()["envResolution"]
200
+ prompt = tmp_path / "prompt.md"
201
+ prompt.write_text("hello", encoding="utf-8")
202
+
203
+ claude = build_ai_session_command(
204
+ AISessionConfig(
205
+ cli_command="claude",
206
+ platform="claude",
207
+ model=None,
208
+ prompt_path=prompt,
209
+ cwd=tmp_path,
210
+ verbose=True,
211
+ )
212
+ )
213
+ assert ("--verbose" in claude.argv) is snapshot["verboseAndStreamJson"]["claudeVerboseHasFlag"]
214
+
215
+ codebuddy = build_ai_session_command(
216
+ AISessionConfig(
217
+ cli_command="cbc",
218
+ platform="codebuddy",
219
+ model=None,
220
+ prompt_path=prompt,
221
+ cwd=tmp_path,
222
+ use_stream_json=True,
223
+ )
224
+ )
225
+ assert ("--output-format" in codebuddy.argv and "stream-json" in codebuddy.argv) is snapshot["verboseAndStreamJson"]["codebuddyStreamJsonHasFlag"]
226
+
227
+ codex_support = detect_stream_json_support("codex", "codex", lambda command: "Usage: codex exec --json")
228
+ assert codex_support.enabled is True
229
+ assert codex_support.format == "codex-json"
230
+
231
+
232
+ def test_windows_structural_paths_use_installed_python_runtime_layout(tmp_path):
233
+ from prizmkit_runtime.paths import resolve_runtime_paths
234
+
235
+ installed_pipeline = tmp_path / "project" / ".prizmkit" / "dev-pipeline"
236
+ installed_pipeline.mkdir(parents=True)
237
+
238
+ paths = resolve_runtime_paths(pipeline_root=installed_pipeline)
239
+
240
+ assert paths.layout == "installed"
241
+ assert paths.project_root == tmp_path / "project"
242
+ assert paths.pipeline_root == installed_pipeline.resolve()
243
+ assert all("dev-pipeline-windows" not in str(value) for value in paths.path_values())
244
+ assert all(not str(value).endswith(".ps1") for value in paths.path_values())
245
+
246
+
247
+ def test_windows_process_tree_termination_strategy_is_taskkill_backed_without_powershell(monkeypatch, tmp_path):
248
+ from prizmkit_runtime import processes
249
+ from prizmkit_runtime.processes import ProcessHandle, ProcessSpec
250
+
251
+ calls = []
252
+
253
+ class FakeCompleted:
254
+ returncode = 0
255
+ stdout = ""
256
+ stderr = ""
257
+
258
+ def fake_run(command, **kwargs):
259
+ calls.append((tuple(command), kwargs))
260
+ return FakeCompleted()
261
+
262
+ monkeypatch.setattr(processes.os, "name", "nt")
263
+ monkeypatch.setattr(processes.subprocess, "CREATE_NEW_PROCESS_GROUP", 0x200, raising=False)
264
+ monkeypatch.setattr(processes.subprocess, "run", fake_run)
265
+
266
+ spec = ProcessSpec(command=(sys.executable, "-c", "pass"), cwd=tmp_path)
267
+ handle = ProcessHandle(pid=1234, spec=spec, status="running", popen=None)
268
+ launched_flags = processes._creationflags_for_platform(True)
269
+ handle_result = processes.terminate_process_tree(handle, "test-timeout", grace_seconds=0)
270
+
271
+ assert launched_flags == 0x200
272
+ assert processes.platform_termination_strategy() == ("CREATE_NEW_PROCESS_GROUP", "taskkill /PID <pid> /T /F")
273
+ assert handle_result.terminated is True
274
+ assert handle_result.forced is True
275
+ assert calls[-1][0] == ("taskkill", "/PID", "1234", "/T", "/F")
276
+
277
+ calls.clear()
278
+ monkeypatch.setattr(processes, "is_pid_running", lambda pid: True)
279
+ pid_result = processes.terminate_pid_tree(5678, "daemon-stop", grace_seconds=0)
280
+
281
+ assert pid_result.terminated is True
282
+ assert pid_result.forced is True
283
+ assert calls[-1][0] == ("taskkill", "/PID", "5678", "/T", "/F")
284
+
285
+
286
+ def test_runner_models_parse_legacy_args_and_environment(tmp_path):
287
+ from prizmkit_runtime.runner_models import RunnerEnvironment, family_for, parse_invocation
288
+
289
+ paths = _make_paths(tmp_path)
290
+ family = family_for("feature", paths)
291
+ invocation = parse_invocation(
292
+ family,
293
+ "run",
294
+ ("custom-list.json", "F-2", "--features", "F-003:F-001", "--max-infra-retries=5", "--mode", "full", "--critic"),
295
+ )
296
+ env = RunnerEnvironment.from_env(
297
+ {
298
+ "STOP_ON_FAILURE": "1",
299
+ "AUTO_PUSH": "true",
300
+ "DEV_BRANCH": "dev/custom",
301
+ "VERBOSE": "1",
302
+ "STALE_KILL_THRESHOLD": "9",
303
+ "STRICT_BEHAVIOR_CHECK": "false",
304
+ }
305
+ )
306
+
307
+ assert invocation.list_path == Path("custom-list.json")
308
+ assert invocation.item_id == "F-002"
309
+ assert invocation.item_filter == "F-003:F-001"
310
+ assert invocation.max_infra_retries == 5
311
+ assert invocation.mode == "full"
312
+ assert invocation.critic is True
313
+ assert env.stop_on_failure is True
314
+ assert env.auto_push is True
315
+ assert env.dev_branch == "dev/custom"
316
+ assert env.stale_kill_threshold_seconds == 9
317
+ assert env.strict_behavior_check is False
318
+
319
+
320
+ def test_runner_environment_enables_worktree_by_default_and_accepts_falsey_opt_out():
321
+ from prizmkit_runtime.runner_models import RunnerEnvironment
322
+
323
+ assert RunnerEnvironment.from_env({}).use_worktree is True
324
+ assert RunnerEnvironment.from_env({"USE_WORKTREE": "1"}).use_worktree is True
325
+ assert RunnerEnvironment.from_env({"USE_WORKTREE": "true"}).use_worktree is True
326
+ assert RunnerEnvironment.from_env({"USE_WORKTREE": "unexpected"}).use_worktree is True
327
+
328
+ for falsey in ("0", "false", "no", "off"):
329
+ assert RunnerEnvironment.from_env({"USE_WORKTREE": falsey}).use_worktree is False
330
+
331
+
332
+ def test_status_bridge_preserves_feature_filter_selection(tmp_path):
333
+ from prizmkit_runtime.runner_models import family_for, parse_invocation
334
+ from prizmkit_runtime.runner_status import get_next, start_item, update_item
335
+
336
+ paths = _make_paths(tmp_path)
337
+ _write_feature_list(paths)
338
+ family = family_for("feature", paths)
339
+ invocation = parse_invocation(family, "run", ("--features", "F-002:F-003"))
340
+ invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
341
+
342
+ next_result = get_next(family, invocation, paths.project_root)
343
+ assert next_result.ok
344
+ assert isinstance(next_result.data, dict)
345
+ assert next_result.data["feature_id"] == "F-002"
346
+
347
+ start = start_item(family, invocation, "F-002", paths.project_root)
348
+ assert start.ok
349
+ assert start.data["new_status"] == "in_progress"
350
+
351
+ update = update_item(family, invocation, "F-002", "context_overflow", "F-002-session", paths.project_root, active_dev_branch="dev/F-002", base_branch="main", no_progress_count=0, progress_fingerprint={"head_commit": "abc"})
352
+ assert update.ok
353
+ assert update.data["new_status"] == "in_progress"
354
+ assert update.data["restart_policy"] == "context_overflow_continuation"
355
+ assert update.data["context_overflow_count"] == 1
356
+
357
+
358
+ def test_prompt_generation_result_preserves_per_item_model(monkeypatch, tmp_path):
359
+ from prizmkit_runtime.runner_models import SessionPaths, family_for, parse_invocation
360
+ from prizmkit_runtime.runner_prompts import generate_prompt
361
+
362
+ paths = _make_paths(tmp_path)
363
+ family = family_for("bugfix", paths)
364
+ invocation = parse_invocation(family, "run", ())
365
+ invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.bugfix_plan})
366
+ session_paths = SessionPaths.for_item(paths.bugfix_state_dir, "B-001", "B-001-session")
367
+
368
+ def fake_run(command, **kwargs):
369
+ assert str(family.prompt_script) in command
370
+ assert "--bug-id" in command
371
+ return subprocess.CompletedProcess(
372
+ command,
373
+ 0,
374
+ stdout=json.dumps(
375
+ {
376
+ "success": True,
377
+ "output_path": str(session_paths.prompt_path),
378
+ "checkpoint_path": str(paths.project_root / ".prizmkit" / "bugfix" / "B-001" / "workflow-checkpoint.json"),
379
+ "model": "per-item-model",
380
+ "pipeline_mode": "standard",
381
+ "agent_count": 3,
382
+ "critic_enabled": False,
383
+ }
384
+ ),
385
+ stderr="",
386
+ )
387
+
388
+ monkeypatch.setattr(subprocess, "run", fake_run)
389
+
390
+ result = generate_prompt(
391
+ family,
392
+ invocation,
393
+ item_id="B-001",
394
+ session_id="B-001-session",
395
+ run_id="run-test",
396
+ retry_count=0,
397
+ session_paths=session_paths,
398
+ project_root=paths.project_root,
399
+ )
400
+
401
+ assert result.model == "per-item-model"
402
+ assert result.output_path == session_paths.prompt_path
403
+ assert result.artifact_path == paths.project_root / ".prizmkit" / "bugfix" / "B-001"
404
+
405
+
406
+ def test_prompt_generation_passes_explicit_execution_root_to_generator(monkeypatch, tmp_path):
407
+ from prizmkit_runtime.runner_models import SessionPaths, family_for, parse_invocation
408
+ from prizmkit_runtime.runner_prompts import generate_prompt
409
+
410
+ paths = _make_paths(tmp_path)
411
+ family = family_for("feature", paths)
412
+ invocation = parse_invocation(family, "run", ())
413
+ invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
414
+ session_paths = SessionPaths.for_item(paths.feature_state_dir, "F-001", "F-001-session")
415
+ execution_root = tmp_path / "worktrees" / "dev-F-001"
416
+
417
+ def fake_run(command, **kwargs):
418
+ assert kwargs["cwd"] == str(paths.project_root)
419
+ assert "--project-root" in command
420
+ assert command[command.index("--project-root") + 1] == str(execution_root)
421
+ return subprocess.CompletedProcess(
422
+ command,
423
+ 0,
424
+ stdout=json.dumps(
425
+ {
426
+ "success": True,
427
+ "output_path": str(session_paths.prompt_path),
428
+ "checkpoint_path": str(execution_root / ".prizmkit" / "specs" / "001-low" / "workflow-checkpoint.json"),
429
+ "model": "",
430
+ "pipeline_mode": "lite",
431
+ "agent_count": 1,
432
+ "critic_enabled": False,
433
+ }
434
+ ),
435
+ stderr="",
436
+ )
437
+
438
+ monkeypatch.setattr(subprocess, "run", fake_run)
439
+
440
+ result = generate_prompt(
441
+ family,
442
+ invocation,
443
+ item_id="F-001",
444
+ session_id="F-001-session",
445
+ run_id="run-test",
446
+ retry_count=0,
447
+ session_paths=session_paths,
448
+ project_root=paths.project_root,
449
+ execution_root=execution_root,
450
+ )
451
+
452
+ assert result.artifact_path == execution_root / ".prizmkit" / "specs" / "001-low"
453
+
454
+
455
+ def _install_runner_session_fakes(monkeypatch, runners, *, statuses=("success",), update_status="completed"):
456
+ from prizmkit_runtime.runner_models import PromptGenerationResult, SessionClassification
457
+
458
+ prompt_calls = []
459
+ launch_cwds = []
460
+ updates = []
461
+ cleanup_calls = []
462
+ merge_calls = []
463
+ wip_calls = []
464
+ status_queue = list(statuses)
465
+
466
+ monkeypatch.setattr(
467
+ runners,
468
+ "load_runtime_config",
469
+ lambda paths: SimpleNamespace(
470
+ ai_client=SimpleNamespace(command="fake-ai", platform="codebuddy"),
471
+ model="fallback-model",
472
+ effort=None,
473
+ ),
474
+ )
475
+ monkeypatch.setattr(runners, "detect_stream_json_support", lambda command, platform: SimpleNamespace(enabled=False))
476
+ monkeypatch.setattr(runners, "start_item", lambda *args, **kwargs: SimpleNamespace(ok=True, text="", data={"new_status": "in_progress"}))
477
+ monkeypatch.setattr(runners, "commit_pre_branch_bookkeeping", lambda *args, **kwargs: None)
478
+ monkeypatch.setattr(runners, "commit_final_bookkeeping", lambda *args, **kwargs: None)
479
+ monkeypatch.setattr(runners, "current_branch", lambda _root: "main")
480
+ monkeypatch.setattr(runners, "_session_id", lambda item_id: f"{item_id}-session-{len(prompt_calls) + 1}")
481
+ monkeypatch.setattr(
482
+ runners,
483
+ "run_git_command",
484
+ lambda project_root, args: SimpleNamespace(return_code=0, stdout="base-head\n", stderr="", args=tuple(args)),
485
+ )
486
+
487
+ def fake_generate_prompt(family, invocation, **kwargs):
488
+ prompt_calls.append(kwargs)
489
+ session_paths = kwargs["session_paths"]
490
+ execution_root = kwargs.get("execution_root") or kwargs["project_root"]
491
+ return PromptGenerationResult(
492
+ output_path=session_paths.prompt_path,
493
+ model="per-item-model",
494
+ pipeline_mode="lite",
495
+ agent_count=1,
496
+ critic_enabled=False,
497
+ checkpoint_path=execution_root / ".prizmkit" / "specs" / "001-low" / "workflow-checkpoint.json",
498
+ artifact_path=execution_root / ".prizmkit" / "specs" / "001-low",
499
+ raw={},
500
+ )
501
+
502
+ class FakeLauncher:
503
+ def __init__(self, config):
504
+ self.config = config
505
+
506
+ def run(self):
507
+ launch_cwds.append(self.config.cwd)
508
+ return SimpleNamespace(progress_summary=SimpleNamespace(), fatal_error_code="")
509
+
510
+ def fake_classify(*args, **kwargs):
511
+ status = status_queue.pop(0)
512
+ return SessionClassification(
513
+ status,
514
+ reason=status,
515
+ fatal_error_code="context_overflow" if status == "context_overflow" else "",
516
+ progress_fingerprint={"head_commit": status},
517
+ no_progress_count=0,
518
+ )
519
+
520
+ def fake_update(family, invocation, item_id, session_status, session_id, project_root, **metadata):
521
+ updates.append((session_status, session_id, metadata))
522
+ return SimpleNamespace(ok=True, text="", data={"new_status": update_status})
523
+
524
+ def fake_cleanup(runtime, *, delete_branch=True):
525
+ cleanup_calls.append((runtime, delete_branch))
526
+ return SimpleNamespace(ok=True, reason="")
527
+
528
+ def fake_merge(runtime, *, auto_push=False):
529
+ merge_calls.append((runtime, auto_push))
530
+ return SimpleNamespace(ok=True, reason="", runtime=runtime, results=())
531
+
532
+ def fake_save_wip(runtime):
533
+ wip_calls.append(runtime)
534
+ return SimpleNamespace(ok=True, plan=None, results=())
535
+
536
+ monkeypatch.setattr(runners, "generate_prompt", fake_generate_prompt)
537
+ monkeypatch.setattr(runners, "AISessionLauncher", FakeLauncher)
538
+ monkeypatch.setattr(runners, "classify_session", fake_classify)
539
+ monkeypatch.setattr(runners, "update_item", fake_update)
540
+ monkeypatch.setattr(runners, "guarded_worktree_remove", fake_cleanup, raising=False)
541
+ monkeypatch.setattr(runners, "merge_linked_worktree", fake_merge, raising=False)
542
+ monkeypatch.setattr(runners, "worktree_save_wip", fake_save_wip, raising=False)
543
+ return SimpleNamespace(
544
+ prompt_calls=prompt_calls,
545
+ launch_cwds=launch_cwds,
546
+ updates=updates,
547
+ cleanup_calls=cleanup_calls,
548
+ merge_calls=merge_calls,
549
+ wip_calls=wip_calls,
550
+ )
551
+
552
+
553
+ @pytest.mark.parametrize(("kind", "item_id", "branch"), [("feature", "F-001", "dev/F-001-test"), ("bugfix", "B-001", "bugfix/B-001-test")])
554
+ def test_feature_and_bugfix_default_worktree_launches_from_worktree_and_renders_worktree_root(monkeypatch, tmp_path, kind, item_id, branch):
555
+ from prizmkit_runtime import runners
556
+ from prizmkit_runtime.runner_models import family_for, parse_invocation
557
+
558
+ monkeypatch.setenv("DEV_BRANCH", branch)
559
+ paths = _make_paths(tmp_path / kind)
560
+ family = family_for(kind, paths)
561
+ invocation = parse_invocation(family, "run", ())
562
+ invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan if kind == "feature" else paths.bugfix_plan})
563
+ observed = _install_runner_session_fakes(monkeypatch, runners)
564
+ setup_calls = []
565
+
566
+ def fake_setup(runtime):
567
+ setup_calls.append(runtime)
568
+ runtime.worktree_path.mkdir(parents=True, exist_ok=True)
569
+ return SimpleNamespace(ok=True, runtime=runtime, reason="")
570
+
571
+ monkeypatch.setattr(runners, "ensure_linked_worktree", fake_setup, raising=False)
572
+
573
+ status = runners._process_item(family, invocation, item_id, paths, initial_metadata={})
574
+
575
+ expected_worktree = paths.state_dir / "worktrees" / branch.replace("/", "-")
576
+ assert status == "completed"
577
+ assert setup_calls[0].worktree_path == expected_worktree.resolve()
578
+ assert observed.launch_cwds == [expected_worktree.resolve()]
579
+ assert observed.prompt_calls[0]["project_root"] == paths.project_root
580
+ assert observed.prompt_calls[0]["execution_root"] == expected_worktree.resolve()
581
+
582
+
583
+ def test_successful_worktree_session_merges_cleans_branch_and_honors_auto_push(monkeypatch, tmp_path):
584
+ from prizmkit_runtime import runners
585
+ from prizmkit_runtime.runner_models import family_for, parse_invocation
586
+
587
+ monkeypatch.setenv("DEV_BRANCH", "dev/F-001-test")
588
+ monkeypatch.setenv("AUTO_PUSH", "1")
589
+ paths = _make_paths(tmp_path)
590
+ family = family_for("feature", paths)
591
+ invocation = parse_invocation(family, "run", ())
592
+ invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
593
+ observed = _install_runner_session_fakes(monkeypatch, runners)
594
+ monkeypatch.setattr(runners, "ensure_linked_worktree", lambda runtime: SimpleNamespace(ok=True, runtime=runtime, reason=""), raising=False)
595
+
596
+ status = runners._process_item(family, invocation, "F-001", paths, initial_metadata={})
597
+
598
+ assert status == "completed"
599
+ assert observed.merge_calls and observed.merge_calls[0][1] is True
600
+ assert observed.cleanup_calls and observed.cleanup_calls[0][1] is True
601
+
602
+
603
+ def test_worktree_merge_conflict_writes_session_status_and_updates_merge_conflict(monkeypatch, tmp_path):
604
+ from prizmkit_runtime import runners
605
+ from prizmkit_runtime.runner_models import family_for, parse_invocation
606
+
607
+ monkeypatch.setenv("DEV_BRANCH", "dev/F-001-test")
608
+ paths = _make_paths(tmp_path)
609
+ family = family_for("feature", paths)
610
+ invocation = parse_invocation(family, "run", ())
611
+ invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
612
+ observed = _install_runner_session_fakes(monkeypatch, runners)
613
+ monkeypatch.setattr(runners, "ensure_linked_worktree", lambda runtime: SimpleNamespace(ok=True, runtime=runtime, reason=""), raising=False)
614
+ monkeypatch.setattr(runners, "merge_linked_worktree", lambda runtime, *, auto_push=False: SimpleNamespace(ok=False, reason="merge_conflict", runtime=runtime, results=()), raising=False)
615
+
616
+ status = runners._process_item(family, invocation, "F-001", paths, initial_metadata={})
617
+
618
+ assert status == "completed"
619
+ assert observed.updates[-1][0] == "merge_conflict"
620
+ assert observed.cleanup_calls[-1][1] is False
621
+ session_status = json.loads((paths.feature_state_dir / "F-001" / "sessions" / "F-001-session-1" / "session-status.json").read_text(encoding="utf-8"))
622
+ assert session_status["status"] == "merge_conflict"
623
+ assert session_status["task_id"] == "F-001"
624
+ assert session_status["task_type"] == "feature"
625
+ assert session_status["base_branch"] == "main"
626
+ assert session_status["active_dev_branch"] == "dev/F-001-test"
627
+ assert session_status["worktree_path"].endswith("dev-F-001-test")
628
+
629
+
630
+ def test_terminal_worktree_failure_saves_wip_and_removes_worktree_without_deleting_branch(monkeypatch, tmp_path):
631
+ from prizmkit_runtime import runners
632
+ from prizmkit_runtime.runner_models import family_for, parse_invocation
633
+
634
+ monkeypatch.setenv("DEV_BRANCH", "dev/F-001-test")
635
+ paths = _make_paths(tmp_path)
636
+ family = family_for("feature", paths)
637
+ invocation = parse_invocation(family, "run", ())
638
+ invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
639
+ observed = _install_runner_session_fakes(monkeypatch, runners, statuses=("crashed",), update_status="failed")
640
+ monkeypatch.setattr(runners, "ensure_linked_worktree", lambda runtime: SimpleNamespace(ok=True, runtime=runtime, reason=""), raising=False)
641
+
642
+ status = runners._process_item(family, invocation, "F-001", paths, initial_metadata={})
643
+
644
+ assert status == "failed"
645
+ assert observed.wip_calls
646
+ assert observed.cleanup_calls[-1][1] is False
647
+ assert observed.updates[-1][0] == "crashed"
648
+
649
+
650
+ def test_default_worktree_materializes_ignored_support_assets_before_prompt_and_launch(monkeypatch, tmp_path):
651
+ from prizmkit_runtime import runners
652
+ from prizmkit_runtime.paths import resolve_runtime_paths
653
+ from prizmkit_runtime.runner_models import PromptGenerationResult, SessionClassification, family_for, parse_invocation
654
+
655
+ project = tmp_path / "repo"
656
+ project.mkdir()
657
+ subprocess.run(["git", "init", "-b", "main"], cwd=project, check=True, stdout=subprocess.DEVNULL)
658
+ subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=project, check=True)
659
+ subprocess.run(["git", "config", "user.name", "Test"], cwd=project, check=True)
660
+ (project / ".gitignore").write_text(".claude/\n.prizmkit/\n", encoding="utf-8")
661
+ (project / "tracked.txt").write_text("tracked\n", encoding="utf-8")
662
+ subprocess.run(["git", "add", ".gitignore", "tracked.txt"], cwd=project, check=True)
663
+ subprocess.run(["git", "commit", "-m", "base"], cwd=project, check=True, stdout=subprocess.DEVNULL)
664
+
665
+ (project / ".claude" / "agents").mkdir(parents=True)
666
+ (project / ".claude" / "agents" / "prizm-dev-team-dev.md").write_text("dev agent", encoding="utf-8")
667
+ (project / ".claude" / "team-info.json").write_text("{}", encoding="utf-8")
668
+ (project / ".prizmkit" / "prizm-docs").mkdir(parents=True)
669
+ (project / ".prizmkit" / "prizm-docs" / "root.prizm").write_text("PRIZM_VERSION: 1\n", encoding="utf-8")
670
+ (project / ".prizmkit" / "dev-pipeline" / "scripts").mkdir(parents=True)
671
+ (project / ".prizmkit" / "dev-pipeline" / "scripts" / "init-dev-team.py").write_text("# init", encoding="utf-8")
672
+ (project / ".prizmkit" / "config.json").write_text("{}", encoding="utf-8")
673
+ (project / ".prizmkit" / "manifest.json").write_text("{}", encoding="utf-8")
674
+
675
+ paths = resolve_runtime_paths(pipeline_root=PIPELINE_ROOT, project_root=project)
676
+ family = family_for("feature", paths)
677
+ invocation = parse_invocation(family, "run", ())
678
+ invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
679
+ launch_cwds = []
680
+
681
+ monkeypatch.setenv("DEV_BRANCH", "dev/F-001-support-assets")
682
+ monkeypatch.setattr(
683
+ runners,
684
+ "load_runtime_config",
685
+ lambda paths: SimpleNamespace(
686
+ ai_client=SimpleNamespace(command="fake-ai", platform="codebuddy"),
687
+ model="fallback-model",
688
+ effort=None,
689
+ ),
690
+ )
691
+ monkeypatch.setattr(runners, "detect_stream_json_support", lambda command, platform: SimpleNamespace(enabled=False))
692
+ monkeypatch.setattr(runners, "start_item", lambda *args, **kwargs: SimpleNamespace(ok=True, text="", data={"new_status": "in_progress"}))
693
+ monkeypatch.setattr(runners, "commit_pre_branch_bookkeeping", lambda *args, **kwargs: None)
694
+ monkeypatch.setattr(runners, "commit_final_bookkeeping", lambda *args, **kwargs: None)
695
+ monkeypatch.setattr(runners, "_session_id", lambda item_id: f"{item_id}-session")
696
+ monkeypatch.setattr(runners, "update_item", lambda *args, **kwargs: SimpleNamespace(ok=True, text="", data={"new_status": "completed"}))
697
+
698
+ def fake_generate_prompt(family, invocation, **kwargs):
699
+ execution_root = kwargs["execution_root"]
700
+ assert execution_root != project
701
+ assert (execution_root / ".claude" / "agents" / "prizm-dev-team-dev.md").is_file()
702
+ assert (execution_root / ".prizmkit" / "prizm-docs" / "root.prizm").is_file()
703
+ assert (execution_root / ".prizmkit" / "config.json").is_file()
704
+ assert (execution_root / ".prizmkit" / "manifest.json").is_file()
705
+ assert (execution_root / ".prizmkit" / "dev-pipeline" / "scripts" / "init-dev-team.py").is_file()
706
+ assert not (execution_root / ".prizmkit" / "dev-pipeline" / "run-feature.sh").exists()
707
+ session_paths = kwargs["session_paths"]
708
+ return PromptGenerationResult(
709
+ output_path=session_paths.prompt_path,
710
+ model="",
711
+ pipeline_mode="lite",
712
+ agent_count=1,
713
+ critic_enabled=False,
714
+ checkpoint_path=execution_root / ".prizmkit" / "specs" / "001-support" / "workflow-checkpoint.json",
715
+ artifact_path=execution_root / ".prizmkit" / "specs" / "001-support",
716
+ raw={},
717
+ )
718
+
719
+ class FakeLauncher:
720
+ def __init__(self, config):
721
+ self.config = config
722
+
723
+ def run(self):
724
+ launch_cwds.append(self.config.cwd)
725
+ return SimpleNamespace(progress_summary=SimpleNamespace(), fatal_error_code="")
726
+
727
+ monkeypatch.setattr(runners, "generate_prompt", fake_generate_prompt)
728
+ monkeypatch.setattr(runners, "AISessionLauncher", FakeLauncher)
729
+ monkeypatch.setattr(
730
+ runners,
731
+ "classify_session",
732
+ lambda *args, **kwargs: SessionClassification(
733
+ "success", reason="success", fatal_error_code="",
734
+ progress_fingerprint={"head_commit": "success"}, no_progress_count=0,
735
+ ),
736
+ )
737
+
738
+ status = runners._process_item(family, invocation, "F-001", paths, initial_metadata={})
739
+
740
+ assert status == "completed"
741
+ assert launch_cwds and launch_cwds[0] == paths.state_dir / "worktrees" / "dev-F-001-support-assets"
742
+ assert not launch_cwds[0].exists()
743
+
744
+
745
+ def test_use_worktree_false_preserves_main_worktree_branch_launch(monkeypatch, tmp_path):
746
+ from prizmkit_runtime import runners
747
+ from prizmkit_runtime.runner_models import family_for, parse_invocation
748
+
749
+ monkeypatch.setenv("USE_WORKTREE", "0")
750
+ monkeypatch.setenv("DEV_BRANCH", "dev/F-001-test")
751
+ paths = _make_paths(tmp_path)
752
+ family = family_for("feature", paths)
753
+ invocation = parse_invocation(family, "run", ())
754
+ invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
755
+ observed = _install_runner_session_fakes(monkeypatch, runners)
756
+ plan_names = []
757
+
758
+ def fake_run_git_plan(project_root, plan):
759
+ plan_names.append(plan.name)
760
+ return SimpleNamespace(ok=True, plan=plan, results=())
761
+
762
+ monkeypatch.setattr(runners, "run_git_plan", fake_run_git_plan)
763
+ monkeypatch.setattr(runners, "ensure_linked_worktree", lambda runtime: (_ for _ in ()).throw(AssertionError("worktree setup should not run")), raising=False)
764
+
765
+ status = runners._process_item(family, invocation, "F-001", paths, initial_metadata={})
766
+
767
+ assert status == "completed"
768
+ assert observed.launch_cwds == [paths.project_root]
769
+ assert observed.prompt_calls[0].get("execution_root") == paths.project_root
770
+ assert "branch_create" in plan_names
771
+ assert "branch_merge" in plan_names
772
+
773
+
774
+ def test_context_overflow_reuses_worktree_and_skips_cleanup_until_terminal_outcome(monkeypatch, tmp_path):
775
+ from prizmkit_runtime import runners
776
+ from prizmkit_runtime.runner_models import family_for, parse_invocation
777
+
778
+ monkeypatch.setenv("DEV_BRANCH", "dev/F-001-test")
779
+ paths = _make_paths(tmp_path)
780
+ family = family_for("feature", paths)
781
+ invocation = parse_invocation(family, "run", ())
782
+ invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
783
+ observed = _install_runner_session_fakes(monkeypatch, runners, statuses=("context_overflow", "success"))
784
+ setup_calls = []
785
+ monkeypatch.setattr(runners, "ensure_linked_worktree", lambda runtime: (setup_calls.append(runtime) or SimpleNamespace(ok=True, runtime=runtime, reason="")), raising=False)
786
+
787
+ status = runners._process_item(family, invocation, "F-001", paths, initial_metadata={})
788
+
789
+ assert status == "completed"
790
+ assert len(setup_calls) == 1
791
+ assert observed.launch_cwds[0] == observed.launch_cwds[1]
792
+ assert observed.prompt_calls[0]["execution_root"] == observed.prompt_calls[1]["execution_root"]
793
+ assert observed.prompt_calls[0].get("continuation") is None
794
+ assert observed.prompt_calls[1]["continuation"]["previous_session_id"] == "F-001-session-1"
795
+ assert observed.updates[0][0] == "context_overflow"
796
+ assert len(observed.cleanup_calls) == 1
797
+ assert observed.cleanup_calls[0][1] is True
798
+
799
+
800
+ def test_classification_preserves_success_before_context_overflow(tmp_path):
801
+ from prizmkit_runtime.runner_classification import classify_session
802
+ from prizmkit_runtime.sessions import AISessionCommand, AISessionResult
803
+ from prizmkit_runtime.status import ProgressSummary
804
+
805
+ subprocess.run(["git", "init", "-b", "main"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
806
+ subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=tmp_path, check=True)
807
+ subprocess.run(["git", "config", "user.name", "Test"], cwd=tmp_path, check=True)
808
+ (tmp_path / "base.txt").write_text("base", encoding="utf-8")
809
+ subprocess.run(["git", "add", "base.txt"], cwd=tmp_path, check=True)
810
+ subprocess.run(["git", "commit", "-m", "base"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
811
+ base_head = subprocess.run(["git", "rev-parse", "HEAD"], cwd=tmp_path, check=True, stdout=subprocess.PIPE, text=True).stdout.strip()
812
+ (tmp_path / "done.txt").write_text("done", encoding="utf-8")
813
+ subprocess.run(["git", "add", "done.txt"], cwd=tmp_path, check=True)
814
+ subprocess.run(["git", "commit", "-m", "done"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
815
+ log = tmp_path / "session.log"
816
+ log.write_text("API Error: input exceeds the context window", encoding="utf-8")
817
+
818
+ raw = AISessionResult(
819
+ command=AISessionCommand(("fake",), None, "codebuddy", "stdin"),
820
+ exit_code=1,
821
+ timed_out=False,
822
+ termination_reason=None,
823
+ stale_marker=None,
824
+ fatal_error_code="context_overflow",
825
+ progress_summary=ProgressSummary(fatal_error_code="context_overflow"),
826
+ session_log=log,
827
+ backup_log=tmp_path / "backup.log",
828
+ log_recovery=None,
829
+ started_epoch=0,
830
+ ended_epoch=1,
831
+ )
832
+
833
+ classification = classify_session(raw, project_root=tmp_path, base_head=base_head)
834
+ assert classification.session_status == "success"
835
+ assert classification.reason == "commit_since_base"
836
+
837
+
838
+ def test_classification_stalls_after_repeated_no_progress_context_overflow(tmp_path):
839
+ from prizmkit_runtime.runner_classification import classify_session, progress_fingerprint
840
+ from prizmkit_runtime.sessions import AISessionCommand, AISessionResult
841
+ from prizmkit_runtime.status import ProgressSummary
842
+
843
+ log = tmp_path / "session.log"
844
+ log.write_text("PRIZMKIT_FATAL_ERROR=context_overflow", encoding="utf-8")
845
+ raw = AISessionResult(
846
+ command=AISessionCommand(("fake",), None, "codebuddy", "stdin"),
847
+ exit_code=1,
848
+ timed_out=False,
849
+ termination_reason=None,
850
+ stale_marker=None,
851
+ fatal_error_code="context_overflow",
852
+ progress_summary=ProgressSummary(fatal_error_code="context_overflow"),
853
+ session_log=log,
854
+ backup_log=tmp_path / "backup.log",
855
+ log_recovery=None,
856
+ started_epoch=0,
857
+ ended_epoch=1,
858
+ )
859
+ previous = progress_fingerprint(tmp_path)
860
+
861
+ classification = classify_session(
862
+ raw,
863
+ project_root=tmp_path,
864
+ base_head="",
865
+ previous_fingerprint=previous,
866
+ previous_no_progress_count=1,
867
+ )
868
+
869
+ assert classification.session_status == "stalled_context_continuation"
870
+ assert classification.no_progress_count == 2
871
+
872
+
873
+ def test_recovery_detector_resolver_prefers_installed_platform_asset(tmp_path):
874
+ from prizmkit_runtime.paths import resolve_runtime_paths
875
+ from prizmkit_runtime.runner_recovery import RecoveryDetectorResolver
876
+
877
+ project = tmp_path / "project"
878
+ agents = project / ".agents" / "skills" / "recovery-workflow" / "scripts"
879
+ claude = project / ".claude" / "command-assets" / "recovery-workflow" / "scripts"
880
+ agents.mkdir(parents=True)
881
+ claude.mkdir(parents=True)
882
+ agent_detector = agents / "detect-recovery-state.py"
883
+ claude_detector = claude / "detect-recovery-state.py"
884
+ agent_detector.write_text("# agent", encoding="utf-8")
885
+ claude_detector.write_text("# claude", encoding="utf-8")
886
+ paths = resolve_runtime_paths(pipeline_root=PIPELINE_ROOT, project_root=project)
887
+
888
+ assert RecoveryDetectorResolver(paths).resolve() == agent_detector
889
+
890
+
891
+ def test_cli_family_status_uses_python_dispatch_not_transition_description(tmp_path):
892
+ paths = _make_paths(tmp_path)
893
+ _write_feature_list(paths)
894
+ result = subprocess.run(
895
+ [PYTHON_310_PLUS, str(CLI_PATH), "--project-root", str(paths.project_root), "feature", "status"],
896
+ cwd=REPO_ROOT,
897
+ stdout=subprocess.PIPE,
898
+ stderr=subprocess.PIPE,
899
+ text=True,
900
+ check=False,
901
+ )
902
+
903
+ assert result.returncode == 0
904
+ assert "Python feature status" in result.stdout
905
+ assert "Transition command contract" not in result.stdout
906
+
907
+
908
+ def _write_family_case(paths, kind, item_id=""):
909
+ item_id = item_id or {"feature": "F-001", "bugfix": "B-001", "refactor": "R-001"}[kind]
910
+ if kind == "feature":
911
+ paths.feature_plan.write_text(json.dumps({"features": [{"id": item_id, "title": "Reset Thing", "status": "failed", "dependencies": []}]}), encoding="utf-8")
912
+ state_dir = paths.feature_state_dir
913
+ plan = paths.feature_plan
914
+ elif kind == "bugfix":
915
+ paths.bugfix_plan.write_text(json.dumps({"bugs": [{"id": item_id, "title": "Reset Bug", "description": "d", "severity": "medium", "priority": "medium", "status": "failed"}]}), encoding="utf-8")
916
+ state_dir = paths.bugfix_state_dir
917
+ plan = paths.bugfix_plan
918
+ else:
919
+ paths.refactor_plan.write_text(json.dumps({"refactors": [{"id": item_id, "title": "Reset Refactor", "description": "d", "complexity": "medium", "priority": "medium", "status": "failed", "dependencies": []}]}), encoding="utf-8")
920
+ state_dir = paths.refactor_state_dir
921
+ plan = paths.refactor_plan
922
+ state_dir.mkdir(parents=True, exist_ok=True)
923
+ (state_dir / "pipeline.json").write_text("{}", encoding="utf-8")
924
+ item_state = state_dir / item_id
925
+ item_state.mkdir(parents=True, exist_ok=True)
926
+ (item_state / "status.json").write_text(
927
+ json.dumps(
928
+ {
929
+ "status": "in_progress",
930
+ "retry_count": 2,
931
+ "infra_error_count": 1,
932
+ "last_infra_error_session_id": "infra-1",
933
+ "last_session_id": "sess-1",
934
+ "sessions": ["sess-1"],
935
+ "continuation_pending": True,
936
+ "continuation_reason": "context_overflow",
937
+ "active_dev_branch": f"{kind}/{item_id}-branch",
938
+ "continuation_summary_path": str(paths.project_root / ".prizmkit" / ("specs" if kind == "feature" else kind) / item_id / "continuation-summary.md"),
939
+ }
940
+ ),
941
+ encoding="utf-8",
942
+ )
943
+ return plan, state_dir, item_state / "status.json"
944
+
945
+
946
+ def test_python_reset_warns_before_mutation_and_matches_snapshot_cleanup(tmp_path):
947
+ from prizmkit_runtime.reset import run_reset_command
948
+
949
+ snapshot = json.loads((REPO_ROOT / "tests" / "fixtures" / "dev-pipeline-behavior-snapshots" / "unix-runtime.json").read_text(encoding="utf-8"))
950
+ for kind, item_id in [("feature", "F-001"), ("bugfix", "B-001"), ("refactor", "R-001")]:
951
+ paths = _make_paths(tmp_path / kind)
952
+ _plan, _state_dir, status_file = _write_family_case(paths, kind, item_id)
953
+ result = run_reset_command(kind, (item_id,), paths)
954
+ output = result.render()
955
+ reset_marker = f"Resetting {item_id} status"
956
+
957
+ assert result.exit_code == snapshot["resetBehavior"]["manualResetWarningOrder"][kind]["exitCode"]
958
+ assert "Continuation pending" in output
959
+ assert "Pending continuation branch" in output
960
+ assert output.index("Continuation pending") < output.index(reset_marker)
961
+ runtime = json.loads(status_file.read_text(encoding="utf-8"))
962
+ expected_runtime = snapshot["statusUpdaterBehavior"]["resetCleanMetadataCleanup"][kind]["reset"]["runtime"]
963
+ for key, value in expected_runtime.items():
964
+ assert runtime.get(key) == value
965
+ assert "continuation_pending" not in runtime
966
+
967
+
968
+ def test_python_clean_reset_deletes_continuation_artifacts_for_all_families(tmp_path):
969
+ from prizmkit_runtime.reset import run_reset_command
970
+
971
+ snapshot = json.loads((REPO_ROOT / "tests" / "fixtures" / "dev-pipeline-behavior-snapshots" / "unix-runtime.json").read_text(encoding="utf-8"))
972
+ cases = [("feature", "F-001", "specs", "001-reset-thing"), ("bugfix", "B-001", "bugfix", "B-001"), ("refactor", "R-001", "refactor", "R-001")]
973
+ for kind, item_id, artifact_root, artifact_name in cases:
974
+ paths = _make_paths(tmp_path / f"clean-{kind}")
975
+ _plan, _state_dir, status_file = _write_family_case(paths, kind, item_id)
976
+ artifact_dir = paths.project_root / ".prizmkit" / artifact_root / artifact_name
977
+ artifact_dir.mkdir(parents=True, exist_ok=True)
978
+ continuation = artifact_dir / "continuation-summary.md"
979
+ continuation.write_text("summary", encoding="utf-8")
980
+ args = (item_id, "--clean")
981
+ result = run_reset_command(kind, args, paths)
982
+
983
+ assert result.exit_code == snapshot["resetBehavior"]["cleanResetDeletesContinuationArtifacts"][kind]["exitCode"]
984
+ assert continuation.exists() is False
985
+ runtime = json.loads(status_file.read_text(encoding="utf-8"))
986
+ assert runtime.get("retry_count") == 0
987
+ assert runtime.get("infra_error_count") == 0
988
+ assert "continuation_pending" not in runtime
989
+
990
+
991
+ def test_python_context_overflow_runner_path_does_not_import_reset():
992
+ runner_text = (PIPELINE_ROOT / "prizmkit_runtime" / "runners.py").read_text(encoding="utf-8")
993
+
994
+ assert "from .reset" not in runner_text
995
+ assert "run_reset_command" not in runner_text
996
+ assert '"context_overflow"' in runner_text
997
+ assert "update_item" in runner_text
998
+
999
+
1000
+ def test_python_daemon_stale_pid_matches_snapshot(tmp_path):
1001
+ from prizmkit_runtime.daemon import run_daemon_command
1002
+
1003
+ snapshot = json.loads((REPO_ROOT / "tests" / "fixtures" / "dev-pipeline-behavior-snapshots" / "unix-runtime.json").read_text(encoding="utf-8"))
1004
+ for kind in ("feature", "bugfix", "refactor"):
1005
+ paths = _make_paths(tmp_path / f"daemon-{kind}")
1006
+ family_state = {"feature": paths.feature_state_dir, "bugfix": paths.bugfix_state_dir, "refactor": paths.refactor_state_dir}[kind]
1007
+ family_state.mkdir(parents=True, exist_ok=True)
1008
+ pid_file = family_state / ".pipeline.pid"
1009
+ pid_file.write_text("999999\n", encoding="utf-8")
1010
+ result = run_daemon_command(kind, "status", (), paths)
1011
+ expected = snapshot["daemonBehavior"]["stalePidStatusByTaskType"][kind]
1012
+
1013
+ assert result.exit_code == expected["exitCode"]
1014
+ assert json.loads(result.render())["running"] is False
1015
+ assert pid_file.exists() is False
1016
+ assert "Cleaned stale PID file" in (result.stderr or "")
1017
+
1018
+
1019
+ def test_python_daemon_controlled_start_status_stop_matches_snapshot(tmp_path):
1020
+ from prizmkit_runtime.daemon import run_daemon_command
1021
+ from prizmkit_runtime.paths import resolve_runtime_paths
1022
+
1023
+ snapshot = json.loads((REPO_ROOT / "tests" / "fixtures" / "dev-pipeline-behavior-snapshots" / "unix-runtime.json").read_text(encoding="utf-8"))["daemonBehavior"]["controlledStartStop"]
1024
+ project = tmp_path / "project"
1025
+ pipeline = tmp_path / "runtime"
1026
+ pipeline.mkdir(parents=True)
1027
+ cli = pipeline / "cli.py"
1028
+ cli.write_text(
1029
+ "#!/usr/bin/env python3\n"
1030
+ "import os, sys, time\n"
1031
+ "print('payload env=' + os.environ.get('FOO', ''))\n"
1032
+ "print('payload args=' + ' '.join(sys.argv[1:]))\n"
1033
+ "sys.stdout.flush()\n"
1034
+ "time.sleep(30)\n",
1035
+ encoding="utf-8",
1036
+ )
1037
+ cli.chmod(0o755)
1038
+ paths = resolve_runtime_paths(pipeline_root=pipeline, project_root=project)
1039
+ paths.plans_dir.mkdir(parents=True)
1040
+ paths.feature_plan.write_text(json.dumps({"features": [{"id": "F-001", "title": "A", "status": "pending"}]}), encoding="utf-8")
1041
+
1042
+ start = run_daemon_command("feature", "start", ("--env", "FOO=bar", "--features", "F-001"), paths)
1043
+ try:
1044
+ start_payload = json.loads(start.render())
1045
+ status = run_daemon_command("feature", "status", (), paths)
1046
+ status_payload = json.loads(status.render())
1047
+ finally:
1048
+ stop = run_daemon_command("feature", "stop", (), paths)
1049
+
1050
+ assert start.exit_code == snapshot["startExitCode"]
1051
+ assert start_payload["success"] is snapshot["startSuccess"]
1052
+ assert isinstance(start_payload["pid"], int)
1053
+ assert (paths.feature_state_dir / ".pipeline.pid").exists() is snapshot["pidFileExistsAfterStop"]
1054
+ assert status.exit_code == snapshot["statusWhileRunningExitCode"]
1055
+ assert status_payload["running"] is snapshot["statusWhileRunningReportsRunning"]
1056
+ assert isinstance(status_payload["pid"], int)
1057
+ assert status_payload["log_file"].endswith("pipeline-daemon.log")
1058
+ assert stop.exit_code == snapshot["stopExitCode"]
1059
+ assert json.loads(stop.render())["message"] in {"stopped", "already exited", "not running"}
1060
+ log_text = (paths.feature_state_dir / "pipeline-daemon.log").read_text(encoding="utf-8")
1061
+ assert "FOO=bar" in log_text or "payload env=bar" in log_text
1062
+ assert "--project-root" in log_text
1063
+
1064
+
1065
+ def test_python_reset_stalled_filter_selects_stalled_context_items(tmp_path):
1066
+ from prizmkit_runtime.reset import run_reset_command
1067
+
1068
+ status_by_kind = {
1069
+ "feature": ("features", "F-001"),
1070
+ "bugfix": ("bugs", "B-001"),
1071
+ "refactor": ("refactors", "R-001"),
1072
+ }
1073
+ for kind, (_collection, item_id) in status_by_kind.items():
1074
+ paths = _make_paths(tmp_path / f"stalled-{kind}")
1075
+ plan, _state_dir, status_file = _write_family_case(paths, kind, item_id)
1076
+ data = json.loads(plan.read_text(encoding="utf-8"))
1077
+ data[{"feature": "features", "bugfix": "bugs", "refactor": "refactors"}[kind]][0]["status"] = "stalled_context_continuation"
1078
+ plan.write_text(json.dumps(data), encoding="utf-8")
1079
+
1080
+ result = run_reset_command(kind, ("--stalled",), paths)
1081
+
1082
+ assert result.exit_code == 0
1083
+ assert f"Reset: {item_id}" in result.render()
1084
+ runtime = json.loads(status_file.read_text(encoding="utf-8"))
1085
+ assert runtime.get("retry_count") == 0
1086
+ assert "continuation_pending" not in runtime
1087
+ updated = json.loads(plan.read_text(encoding="utf-8"))
1088
+ assert updated[{"feature": "features", "bugfix": "bugs", "refactor": "refactors"}[kind]][0]["status"] == "pending"
1089
+
1090
+
1091
+ def test_python_daemon_progress_uses_list_status_without_runtime_files(tmp_path):
1092
+ from prizmkit_runtime.daemon import _progress_summary
1093
+ from prizmkit_runtime.runner_models import family_for
1094
+
1095
+ for kind in ("feature", "bugfix", "refactor"):
1096
+ paths = _make_paths(tmp_path / f"progress-{kind}")
1097
+ if kind == "feature":
1098
+ paths.feature_plan.write_text(
1099
+ json.dumps({"features": [
1100
+ {"id": "F-001", "title": "Done", "status": "completed"},
1101
+ {"id": "F-002", "title": "Failed", "status": "failed"},
1102
+ {"id": "F-003", "title": "Skipped", "status": "auto_skipped"},
1103
+ ]}),
1104
+ encoding="utf-8",
1105
+ )
1106
+ plan = paths.feature_plan
1107
+ elif kind == "bugfix":
1108
+ paths.bugfix_plan.write_text(
1109
+ json.dumps({"bugs": [
1110
+ {"id": "B-001", "title": "Done", "status": "completed"},
1111
+ {"id": "B-002", "title": "Failed", "status": "failed"},
1112
+ {"id": "B-003", "title": "Needs Info", "status": "needs_info"},
1113
+ ]}),
1114
+ encoding="utf-8",
1115
+ )
1116
+ plan = paths.bugfix_plan
1117
+ else:
1118
+ paths.refactor_plan.write_text(
1119
+ json.dumps({"refactors": [
1120
+ {"id": "R-001", "title": "Done", "status": "completed"},
1121
+ {"id": "R-002", "title": "Failed", "status": "failed"},
1122
+ {"id": "R-003", "title": "Skipped", "status": "auto_skipped"},
1123
+ ]}),
1124
+ encoding="utf-8",
1125
+ )
1126
+ plan = paths.refactor_plan
1127
+
1128
+ summary = _progress_summary(family_for(kind, paths), plan)
1129
+
1130
+ assert summary["total"] == 3
1131
+ assert summary["completed"] == 1
1132
+ assert summary["failed"] == 1
1133
+ assert summary["percent"] == 33.3
1134
+ if kind == "bugfix":
1135
+ assert summary["needs_info"] == 1
1136
+ else:
1137
+ assert summary["auto_skipped"] == 1
1138
+