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
@@ -0,0 +1,1045 @@
1
+ """Tests for the unified Python CLI architecture skeleton."""
2
+
3
+ import json
4
+ import os
5
+ import shutil
6
+ import subprocess
7
+ import sys
8
+ import time
9
+ from dataclasses import FrozenInstanceError
10
+ from pathlib import Path
11
+
12
+ import pytest
13
+
14
+ REPO_ROOT = Path(__file__).resolve().parents[2]
15
+ PIPELINE_ROOT = REPO_ROOT / "dev-pipeline"
16
+ CLI_PATH = PIPELINE_ROOT / "cli.py"
17
+ def _python_310_plus() -> str:
18
+ if sys.version_info >= (3, 10):
19
+ return sys.executable
20
+ for executable in ("python3.12", "python3.11", "python3.10"):
21
+ resolved = shutil.which(executable)
22
+ if resolved:
23
+ return resolved
24
+ return sys.executable
25
+
26
+
27
+ PYTHON_310_PLUS = _python_310_plus()
28
+
29
+ if str(PIPELINE_ROOT) not in sys.path:
30
+ sys.path.insert(0, str(PIPELINE_ROOT))
31
+
32
+
33
+ def run_cli(*args):
34
+ return subprocess.run(
35
+ [PYTHON_310_PLUS, str(CLI_PATH), *args],
36
+ cwd=REPO_ROOT,
37
+ text=True,
38
+ stdout=subprocess.PIPE,
39
+ stderr=subprocess.PIPE,
40
+ check=False,
41
+ )
42
+
43
+
44
+ def test_cli_help_exposes_python_runtime_command_groups():
45
+ result = run_cli("--help")
46
+
47
+ assert result.returncode == 0
48
+ assert "canonical Python runtime CLI" in result.stdout
49
+ assert "without shell wrappers" in result.stdout
50
+ for group in [
51
+ "feature",
52
+ "bugfix",
53
+ "refactor",
54
+ "recovery",
55
+ "reset",
56
+ "daemon",
57
+ "status",
58
+ "diagnostics",
59
+ ]:
60
+ assert group in result.stdout
61
+ assert "Traceback" not in result.stderr
62
+
63
+
64
+ def test_module_entrypoint_help_exposes_same_cli():
65
+ env = {**os.environ, "PYTHONPATH": str(PIPELINE_ROOT)}
66
+ result = subprocess.run(
67
+ [PYTHON_310_PLUS, "-m", "prizmkit_runtime", "--help"],
68
+ cwd=REPO_ROOT,
69
+ env=env,
70
+ text=True,
71
+ stdout=subprocess.PIPE,
72
+ stderr=subprocess.PIPE,
73
+ check=False,
74
+ )
75
+
76
+ assert result.returncode == 0
77
+ assert "canonical Python runtime CLI" in result.stdout
78
+ assert "diagnostics" in result.stdout
79
+
80
+
81
+ def test_invalid_arguments_report_usage_without_traceback():
82
+ result = run_cli("unknown-command")
83
+
84
+ assert result.returncode == 2
85
+ assert "usage:" in result.stderr
86
+ assert "invalid choice" in result.stderr
87
+ assert "Traceback" not in result.stderr
88
+
89
+
90
+ def test_python_version_guard_mentions_required_version():
91
+ from prizmkit_runtime.compat import UnsupportedPythonVersion, ensure_supported_python
92
+
93
+ ensure_supported_python((3, 10, 0))
94
+ ensure_supported_python((3, 11, 1))
95
+
96
+ with pytest.raises(UnsupportedPythonVersion) as exc_info:
97
+ ensure_supported_python((3, 9, 18))
98
+
99
+ assert "Python 3.10+" in str(exc_info.value)
100
+ assert "detected Python 3.9" in str(exc_info.value)
101
+
102
+
103
+ def test_runtime_paths_support_framework_source_layout():
104
+ from prizmkit_runtime.paths import RuntimePaths, resolve_runtime_paths
105
+
106
+ paths = resolve_runtime_paths(pipeline_root=PIPELINE_ROOT)
107
+
108
+ assert isinstance(paths, RuntimePaths)
109
+ assert paths.layout == "framework-source"
110
+ assert paths.pipeline_root == PIPELINE_ROOT.resolve()
111
+ assert paths.project_root == REPO_ROOT.resolve()
112
+ assert paths.prizmkit_dir == REPO_ROOT / ".prizmkit"
113
+ assert paths.feature_plan == paths.plans_dir / "feature-list.json"
114
+ assert paths.bugfix_plan == paths.plans_dir / "bug-fix-list.json"
115
+ assert paths.refactor_plan == paths.plans_dir / "refactor-list.json"
116
+ assert paths.feature_state_dir == paths.state_dir / "features"
117
+ assert paths.specs_dir == paths.prizmkit_dir / "specs"
118
+ assert all(isinstance(value, Path) for value in paths.path_values())
119
+
120
+
121
+ def test_runtime_paths_support_installed_layout(tmp_path):
122
+ from prizmkit_runtime.paths import resolve_runtime_paths
123
+
124
+ installed_pipeline = tmp_path / "project" / ".prizmkit" / "dev-pipeline"
125
+ installed_pipeline.mkdir(parents=True)
126
+
127
+ paths = resolve_runtime_paths(pipeline_root=installed_pipeline)
128
+
129
+ assert paths.layout == "installed"
130
+ assert paths.project_root == tmp_path / "project"
131
+ assert paths.prizmkit_dir == tmp_path / "project" / ".prizmkit"
132
+ assert paths.pipeline_root == installed_pipeline.resolve()
133
+ assert paths.logs_dir == paths.state_dir / "logs"
134
+
135
+
136
+ def test_runtime_config_models_environment_and_ai_resolution():
137
+ from prizmkit_runtime.config import AIClientResolution, RuntimeEnvironment, load_runtime_config
138
+ from prizmkit_runtime.paths import resolve_runtime_paths
139
+
140
+ paths = resolve_runtime_paths(pipeline_root=PIPELINE_ROOT)
141
+ config = load_runtime_config(
142
+ paths,
143
+ env={
144
+ "AI_CLI": "claude",
145
+ "PRIZMKIT_PLATFORM": "codex",
146
+ "MODEL": "claude-sonnet-4.6",
147
+ "PRIZMKIT_EFFORT": "full",
148
+ "MAX_RETRIES": "5",
149
+ "PATH": "/usr/bin:/bin",
150
+ },
151
+ )
152
+
153
+ assert config.max_retries == 5
154
+ assert config.model == "claude-sonnet-4.6"
155
+ assert config.effort == "full"
156
+ assert isinstance(config.environment, RuntimeEnvironment)
157
+ assert isinstance(config.ai_client, AIClientResolution)
158
+ assert config.ai_client.command == "claude"
159
+ assert config.ai_client.platform == "claude"
160
+ assert config.ai_client.source == "environment:AI_CLI"
161
+ assert "AI_CLI" in config.ai_client.precedence
162
+ assert ".prizmkit/config.json:ai_cli" in config.ai_client.precedence
163
+ assert config.runtime_mode == "python-runtime-canonical"
164
+
165
+
166
+ def test_runtime_config_matches_canonical_ai_cli_precedence(tmp_path):
167
+ from prizmkit_runtime.config import load_runtime_config
168
+ from prizmkit_runtime.paths import resolve_runtime_paths
169
+
170
+ project = tmp_path / "project"
171
+ pipeline = project / ".prizmkit" / "dev-pipeline"
172
+ pipeline.mkdir(parents=True)
173
+ prizmkit_dir = project / ".prizmkit"
174
+ (prizmkit_dir / "config.json").write_text(json.dumps({"ai_cli": "config-cbc", "platform": "claude"}), encoding="utf-8")
175
+ (prizmkit_dir / "manifest.json").write_text(json.dumps({"platform": "codex"}), encoding="utf-8")
176
+ paths = resolve_runtime_paths(pipeline_root=pipeline)
177
+
178
+ config_cli = load_runtime_config(paths, env={"PATH": ""}).ai_client
179
+ assert config_cli.command == "config-cbc"
180
+ assert config_cli.platform == "codebuddy"
181
+ assert config_cli.source == "prizmkit-config:ai_cli"
182
+
183
+ (prizmkit_dir / "config.json").write_text(json.dumps({"platform": "claude"}), encoding="utf-8")
184
+ manifest_platform = load_runtime_config(paths, env={"PATH": ""}).ai_client
185
+ assert manifest_platform.command is None
186
+ assert manifest_platform.platform == "codex"
187
+ assert manifest_platform.source == "unresolved:prizmkit-manifest:platform"
188
+ assert "codex" in manifest_platform.notes[0]
189
+
190
+ (prizmkit_dir / "manifest.json").unlink()
191
+ (prizmkit_dir / "config.json").write_text("{}", encoding="utf-8")
192
+ legacy_alias = load_runtime_config(paths, env={"CODEBUDDY_CLI": "custom-cbc", "PATH": ""}).ai_client
193
+ assert legacy_alias.command == "custom-cbc"
194
+ assert legacy_alias.platform == "codebuddy"
195
+ assert legacy_alias.source == "environment:CODEBUDDY_CLI"
196
+
197
+
198
+ def test_runtime_config_supplied_env_does_not_leak_ambient_path(tmp_path):
199
+ from prizmkit_runtime.config import load_runtime_config
200
+ from prizmkit_runtime.paths import resolve_runtime_paths
201
+
202
+ isolated_pipeline = tmp_path / "project" / ".prizmkit" / "dev-pipeline"
203
+ isolated_pipeline.mkdir(parents=True)
204
+ paths = resolve_runtime_paths(pipeline_root=isolated_pipeline)
205
+ config = load_runtime_config(paths, env={})
206
+
207
+ assert config.environment.path_entries == ()
208
+ assert config.ai_client.command is None
209
+ assert config.ai_client.source == "unresolved"
210
+
211
+
212
+
213
+ from prizmkit_runtime.commands import list_runtime_commands
214
+
215
+ command_keys = {command.key for command in list_runtime_commands()}
216
+
217
+ expected = {
218
+ ("feature", None, "run"),
219
+ ("feature", None, "status"),
220
+ ("feature", None, "unskip"),
221
+ ("feature", None, "test-cli"),
222
+ ("feature", None, "describe"),
223
+ ("bugfix", None, "run"),
224
+ ("bugfix", None, "status"),
225
+ ("bugfix", None, "unskip"),
226
+ ("bugfix", None, "describe"),
227
+ ("refactor", None, "run"),
228
+ ("refactor", None, "status"),
229
+ ("refactor", None, "unskip"),
230
+ ("refactor", None, "describe"),
231
+ ("recovery", None, "run"),
232
+ ("recovery", None, "detect"),
233
+ ("recovery", None, "describe"),
234
+ ("reset", "feature", "reset"),
235
+ ("reset", "bugfix", "reset"),
236
+ ("reset", "refactor", "reset"),
237
+ ("reset", None, "describe"),
238
+ ("status", "feature", "status"),
239
+ ("status", "bugfix", "status"),
240
+ ("status", "refactor", "status"),
241
+ ("status", "all", "status"),
242
+ ("diagnostics", None, "paths"),
243
+ ("diagnostics", None, "entrypoints"),
244
+ ("diagnostics", None, "version"),
245
+ }
246
+ for target in ["feature", "bugfix", "refactor"]:
247
+ for action in ["start", "stop", "status", "logs", "restart", "describe"]:
248
+ expected.add(("daemon", target, action))
249
+
250
+ assert expected.issubset(command_keys)
251
+
252
+
253
+ def test_python_runtime_command_output_describes_canonical_boundary():
254
+ result = run_cli("feature", "describe")
255
+
256
+ assert result.returncode == 0
257
+ assert "Python runtime command contract" in result.stdout
258
+ assert "canonical PrizmKit Python runtime" in result.stdout
259
+ assert "current entrypoint" in result.stdout
260
+ assert "python-runtime-canonical" in result.stdout
261
+ assert "fully replaces" not in result.stdout.lower()
262
+ assert "shell runtime remains canonical" not in result.stdout
263
+
264
+
265
+ def test_option_first_runtime_arguments_are_forwarded(monkeypatch):
266
+ from prizmkit_runtime import cli as runtime_cli
267
+
268
+ captured = []
269
+
270
+ def fake_handle(group, action, *, target=None, paths=None, legacy_args=()):
271
+ captured.append((group, action, target, legacy_args))
272
+ return runtime_cli.CommandResult(0, "ok", "ok")
273
+
274
+ monkeypatch.setattr(runtime_cli, "ensure_supported_python", lambda: None)
275
+ monkeypatch.setattr(runtime_cli, "handle_runtime_command", fake_handle)
276
+
277
+ assert runtime_cli.main(["recovery", "run", "--dry-run"]) == 0
278
+ assert captured[-1] == ("recovery", "run", None, ("--dry-run",))
279
+
280
+ assert runtime_cli.main(["daemon", "feature", "start", "--mode", "standard"]) == 0
281
+ assert captured[-1] == ("daemon", "start", "feature", ("--mode", "standard"))
282
+
283
+ assert runtime_cli.main(["daemon", "feature", "logs", "--follow"]) == 0
284
+ assert captured[-1] == ("daemon", "logs", "feature", ("--follow",))
285
+
286
+ assert runtime_cli.main(["reset", "feature", "--failed"]) == 0
287
+ assert captured[-1] == ("reset", "reset", "feature", ("--failed",))
288
+
289
+ assert runtime_cli.main(["--project-root", "feature", "feature", "describe"]) == 0
290
+ assert captured[-1] == ("feature", "describe", None, ())
291
+
292
+
293
+ def test_unskip_dispatches_status_updater_not_contract(monkeypatch):
294
+ from prizmkit_runtime import runners
295
+ from prizmkit_runtime.commands import handle_runtime_command
296
+ from prizmkit_runtime.paths import resolve_runtime_paths
297
+
298
+ class FakeStatus:
299
+ return_code = 0
300
+ stdout = "unskipped\n"
301
+ stderr = ""
302
+
303
+ captured = []
304
+
305
+ def fake_run_status_action(family, action, invocation, *, item_id="", project_root=None, **kwargs):
306
+ captured.append((family.kind, action, invocation.item_id, item_id, project_root))
307
+ return FakeStatus()
308
+
309
+ monkeypatch.setattr(runners, "run_status_action", fake_run_status_action)
310
+ paths = resolve_runtime_paths(pipeline_root=PIPELINE_ROOT)
311
+
312
+ result = handle_runtime_command("feature", "unskip", paths=paths, legacy_args=("F-005",))
313
+
314
+ assert result.exit_code == 0
315
+ assert result.title == "Python feature unskip"
316
+ assert "unskipped" in result.render()
317
+ assert "Python runtime command contract" not in result.render()
318
+ assert captured == [("feature", "unskip", "F-005", "F-005", REPO_ROOT.resolve())]
319
+
320
+
321
+ def test_test_cli_reports_ai_resolution_not_contract(monkeypatch):
322
+ from prizmkit_runtime import runners
323
+ from prizmkit_runtime.commands import handle_runtime_command
324
+ from prizmkit_runtime.paths import resolve_runtime_paths
325
+
326
+ class FakeAIClient:
327
+ command = "claude"
328
+ platform = "claude"
329
+ source = "test"
330
+ notes = ()
331
+
332
+ class FakeConfig:
333
+ ai_client = FakeAIClient()
334
+ runtime_mode = "python-runtime-canonical"
335
+ model = "claude-sonnet-4.6"
336
+
337
+ monkeypatch.setattr(runners, "load_runtime_config", lambda paths: FakeConfig())
338
+ paths = resolve_runtime_paths(pipeline_root=PIPELINE_ROOT)
339
+
340
+ result = handle_runtime_command("feature", "test-cli", paths=paths)
341
+ rendered = result.render()
342
+
343
+ assert result.exit_code == 0
344
+ assert result.title == "Python feature AI CLI test"
345
+ assert "detected_cli: claude" in rendered
346
+ assert "Python runtime command contract" not in rendered
347
+
348
+
349
+ def test_diagnostics_paths_and_entrypoints_are_standard_library_cli_actions():
350
+ paths_result = run_cli("diagnostics", "paths")
351
+ entrypoints_result = run_cli("diagnostics", "entrypoints")
352
+
353
+ assert paths_result.returncode == 0
354
+ assert "Runtime path diagnostics" in paths_result.stdout
355
+ assert "pipeline_root:" in paths_result.stdout
356
+ assert entrypoints_result.returncode == 0
357
+ assert "Python runtime entrypoint inventory" in entrypoints_result.stdout
358
+ assert "runtime:cli:" in entrypoints_result.stdout
359
+ assert "run-feature.sh" not in entrypoints_result.stdout
360
+ assert "generate-bootstrap-prompt.py" in entrypoints_result.stdout
361
+
362
+
363
+ def test_interoperability_preserves_existing_utility_mapping():
364
+ from prizmkit_runtime.interoperability import (
365
+ UTILITY_ENTRYPOINTS,
366
+ UTILITY_LIBRARY_ONLY,
367
+ describe_existing_entrypoint,
368
+ )
369
+ from prizmkit_runtime.paths import resolve_runtime_paths
370
+
371
+ paths = resolve_runtime_paths(pipeline_root=PIPELINE_ROOT)
372
+ invocation = describe_existing_entrypoint("utility", "generate-bootstrap-prompt", paths=paths)
373
+
374
+ assert invocation.script_path == PIPELINE_ROOT / "scripts" / "generate-bootstrap-prompt.py"
375
+ assert invocation.arguments == ()
376
+ assert invocation.cwd == paths.project_root
377
+
378
+ help_result = subprocess.run(
379
+ [sys.executable, str(invocation.script_path), "--help"],
380
+ cwd=REPO_ROOT,
381
+ text=True,
382
+ stdout=subprocess.PIPE,
383
+ stderr=subprocess.PIPE,
384
+ check=False,
385
+ )
386
+ assert help_result.returncode == 0
387
+ assert "--feature-list" in help_result.stdout
388
+
389
+ actual_scripts = {path.name for path in (PIPELINE_ROOT / "scripts").glob("*.py")}
390
+ mapped_scripts = {Path(relative_path).name for relative_path in UTILITY_ENTRYPOINTS.values()}
391
+ explicitly_library_only = set(UTILITY_LIBRARY_ONLY)
392
+
393
+ assert actual_scripts == mapped_scripts | explicitly_library_only
394
+ assert explicitly_library_only == {"continuation.py", "utils.py"}
395
+ assert "parse-stream-progress.py" in mapped_scripts
396
+ assert "prizmkit-test-gate.py" in mapped_scripts
397
+
398
+
399
+ def test_boundary_dataclasses_are_cohesive_and_frozen():
400
+ from prizmkit_runtime.gitops import BranchContext, WorktreePolicy
401
+ from prizmkit_runtime.heartbeat import HeartbeatConfig, HeartbeatState
402
+ from prizmkit_runtime.paths import resolve_runtime_paths
403
+ from prizmkit_runtime.processes import ProcessHandle, ProcessSpec
404
+ from prizmkit_runtime.sessions import AISessionConfig, AISessionLauncher
405
+ from prizmkit_runtime.status import StatusTarget, StatusUpdateRequest, default_status_targets
406
+
407
+ paths = resolve_runtime_paths(pipeline_root=PIPELINE_ROOT)
408
+ session = AISessionConfig(
409
+ cli_command="claude",
410
+ platform="claude",
411
+ model="claude-sonnet-4.6",
412
+ prompt_path=paths.state_dir / "prompt.md",
413
+ cwd=paths.project_root,
414
+ )
415
+ process = ProcessSpec(command=("python3", "-m", "prizmkit_runtime"), cwd=paths.project_root)
416
+ handle = ProcessHandle(pid=1234, spec=process, status="described")
417
+ heartbeat = HeartbeatConfig(heartbeat_file=paths.state_dir / ".heartbeat")
418
+ heartbeat_state = HeartbeatState(last_seen_epoch=1.0, stale=False, details="fresh")
419
+ branch = BranchContext(base_branch="main", working_branch="dev/example", project_root=paths.project_root)
420
+ policy = WorktreePolicy(use_worktree=True, cleanup_on_success=False, preserve_on_failure=True)
421
+ targets = default_status_targets(paths)
422
+ status_target = StatusTarget(kind="feature", plan_path=paths.feature_plan, state_dir=paths.feature_state_dir)
423
+ status_request = StatusUpdateRequest(target=status_target, action="status")
424
+
425
+ assert AISessionLauncher(session).describe().startswith("AI session launch boundary")
426
+ assert handle.spec == process
427
+ assert heartbeat_state.details == "fresh"
428
+ assert branch.project_root == paths.project_root
429
+ assert policy.preserve_on_failure is True
430
+ assert targets["feature"].plan_path == paths.feature_plan
431
+ assert status_request.target == status_target
432
+
433
+ with pytest.raises(FrozenInstanceError):
434
+ session.model = "other"
435
+ with pytest.raises(FrozenInstanceError):
436
+ heartbeat.interval_seconds = 10
437
+
438
+
439
+ def test_runtime_config_loads_prizmkit_env_and_validates_effort(tmp_path):
440
+ from prizmkit_runtime.config import load_runtime_config, validate_effort
441
+ from prizmkit_runtime.paths import resolve_runtime_paths
442
+
443
+ project = tmp_path / "project"
444
+ pipeline = project / ".prizmkit" / "dev-pipeline"
445
+ pipeline.mkdir(parents=True)
446
+ prizmkit_dir = project / ".prizmkit"
447
+ (prizmkit_dir / ".env").write_text(
448
+ "AI_CLI=env-claude\nPRIZMKIT_EFFORT=max\nMODEL=env-model\n",
449
+ encoding="utf-8",
450
+ )
451
+ paths = resolve_runtime_paths(pipeline_root=pipeline)
452
+
453
+ config = load_runtime_config(paths, env={"PATH": ""})
454
+
455
+ assert config.ai_client.command == "env-claude"
456
+ assert config.ai_client.platform == "claude"
457
+ assert config.ai_client.source == "environment:AI_CLI"
458
+ assert config.model == "env-model"
459
+ assert config.effort_validation.valid is True
460
+ assert validate_effort("max", "codex").valid is False
461
+ assert "Codex" in validate_effort("max", "codex").message
462
+
463
+
464
+ def test_stream_json_support_detection_matches_provider_rules():
465
+ from prizmkit_runtime.sessions import detect_stream_json_support
466
+
467
+ def help_runner(command):
468
+ if tuple(command) == ("codex", "exec", "--help"):
469
+ return "Usage: codex exec --json"
470
+ if tuple(command) == ("claude", "--help"):
471
+ return "--output-format stream-json"
472
+ return ""
473
+
474
+ codebuddy = detect_stream_json_support("cbc", "codebuddy", help_runner)
475
+ codex = detect_stream_json_support("codex", "codex", help_runner)
476
+ claude = detect_stream_json_support("claude", "claude", help_runner)
477
+ unsupported = detect_stream_json_support("other", "claude", lambda _cmd: "plain help")
478
+
479
+ assert codebuddy.enabled and codebuddy.format == "stream-json"
480
+ assert codex.enabled and codex.format == "codex-json"
481
+ assert claude.enabled and claude.format == "stream-json"
482
+ assert unsupported.enabled is False
483
+
484
+
485
+ def test_ai_session_command_builders_match_unix_runtime_snapshot(tmp_path):
486
+ from prizmkit_runtime.sessions import AISessionConfig, build_ai_session_command
487
+
488
+ snapshot = json.loads((REPO_ROOT / "tests" / "fixtures" / "dev-pipeline-behavior-snapshots" / "unix-runtime.json").read_text(encoding="utf-8"))
489
+ prompt = tmp_path / "prompt.md"
490
+ prompt.write_text("hello", encoding="utf-8")
491
+ project = tmp_path / "project"
492
+ project.mkdir()
493
+
494
+ claude = build_ai_session_command(
495
+ AISessionConfig(
496
+ cli_command="claude",
497
+ platform="claude",
498
+ model="sonnet",
499
+ prompt_path=prompt,
500
+ cwd=project,
501
+ effort="high",
502
+ )
503
+ )
504
+ expected_claude = tuple(snapshot["aiCliCommands"]["claudeCommand"]["argv"])
505
+ assert claude.argv[1:] == expected_claude
506
+ assert claude.stdin_text is None
507
+
508
+ codex = build_ai_session_command(
509
+ AISessionConfig(
510
+ cli_command="codex",
511
+ platform="codex",
512
+ model="codex-model",
513
+ prompt_path=prompt,
514
+ cwd=project,
515
+ effort="xhigh",
516
+ use_stream_json=True,
517
+ )
518
+ )
519
+ expected_prefix = tuple(snapshot["aiCliCommands"]["codexCommand"]["argvPrefix"])
520
+ assert codex.argv[1 : 1 + len(expected_prefix)] == expected_prefix
521
+ assert "--cd" in codex.argv
522
+ assert str(project) in codex.argv
523
+ assert "--json" in codex.argv
524
+ effort_index = codex.argv.index("model_reasoning_effort=xhigh")
525
+ assert codex.argv[effort_index - 1] == "--config"
526
+ assert codex.argv[-1] == "-"
527
+ assert codex.stdin_text == "hello"
528
+
529
+ codebuddy = build_ai_session_command(
530
+ AISessionConfig(
531
+ cli_command="custom-cbc",
532
+ platform="codebuddy",
533
+ model="codebuddy-model",
534
+ prompt_path=prompt,
535
+ cwd=project,
536
+ effort="medium",
537
+ )
538
+ )
539
+ expected_codebuddy = tuple(snapshot["aiCliCommands"]["customWrapperCommand"]["argv"])
540
+ assert codebuddy.argv[1:] == expected_codebuddy
541
+ assert codebuddy.stdin_text == "hello"
542
+
543
+
544
+ def test_progress_parser_adds_required_metadata(tmp_path):
545
+ import importlib.util
546
+
547
+ parser_path = PIPELINE_ROOT / "scripts" / "parse-stream-progress.py"
548
+ spec = importlib.util.spec_from_file_location("parse_stream_progress", parser_path)
549
+ module = importlib.util.module_from_spec(spec)
550
+ assert spec.loader is not None
551
+ spec.loader.exec_module(module)
552
+
553
+ tracker = module.ProgressTracker()
554
+ tracker.process_event({"type": "system", "subtype": "init", "session_id": "claude-session", "cwd": str(tmp_path)})
555
+ tracker.process_event({"type": "assistant", "message": {"content": [{"type": "text", "text": "working"}]}})
556
+ tracker.process_event({"type": "result", "subtype": "success", "stop_reason": "end_turn", "usage": {"input_tokens": 1}, "result": "done"})
557
+ tracker.process_event({"type": "error", "error": {"message": "permission denied by user"}})
558
+ data = tracker.to_dict()
559
+
560
+ assert data["event_type_counts"]["system"] == 1
561
+ assert data["event_type_counts"]["assistant"] == 1
562
+ assert data["claude_session_id"] == "claude-session"
563
+ assert data["result_subtype"] == "success"
564
+ assert data["stop_reason"] == "end_turn"
565
+ assert data["has_usage"] is True
566
+ assert data["permission_denial_count"] == 1
567
+
568
+
569
+ def test_session_dual_writes_progress_and_recovers_backup(tmp_path):
570
+ from prizmkit_runtime.sessions import AISessionConfig, AISessionLauncher, recover_session_log
571
+
572
+ fake_cli = tmp_path / "fake_cli.py"
573
+ fake_cli.write_text(
574
+ "#!/usr/bin/env python3\n"
575
+ "import json, sys\n"
576
+ "print(json.dumps({'type':'system','subtype':'init','session_id':'s1'}), flush=True)\n"
577
+ "print(json.dumps({'type':'result','subtype':'success','stop_reason':'end_turn','usage':{'tokens':1},'result':'ok'}), flush=True)\n",
578
+ encoding="utf-8",
579
+ )
580
+ fake_cli.chmod(0o755)
581
+ prompt = tmp_path / "prompt.md"
582
+ prompt.write_text("hello", encoding="utf-8")
583
+ session_log = tmp_path / "state" / "sessions" / "sess-1" / "logs" / "session.log"
584
+ backup_log = tmp_path / "home" / ".prizmkit" / "session-backups" / "sess-1.log"
585
+ progress = session_log.with_name("progress.json")
586
+
587
+ result = AISessionLauncher(
588
+ AISessionConfig(
589
+ cli_command=str(fake_cli),
590
+ platform="codebuddy",
591
+ model=None,
592
+ prompt_path=prompt,
593
+ cwd=tmp_path,
594
+ log_path=session_log,
595
+ backup_log_path=backup_log,
596
+ progress_path=progress,
597
+ )
598
+ ).run()
599
+
600
+ assert result.exit_code == 0
601
+ assert session_log.exists()
602
+ assert progress.exists()
603
+ progress_data = json.loads(progress.read_text(encoding="utf-8"))
604
+ assert progress_data["event_type_counts"]["result"] == 1
605
+ assert progress_data["claude_session_id"] == "s1"
606
+ assert progress_data["has_usage"] is True
607
+ assert result.log_recovery is not None
608
+
609
+ second_backup = tmp_path / "backup.log"
610
+ second_log = tmp_path / "main.log"
611
+ second_log.write_text("tiny", encoding="utf-8")
612
+ second_backup.write_text("larger recovered log", encoding="utf-8")
613
+ recovery = recover_session_log(second_log, second_backup)
614
+ assert recovery.recovered is True
615
+ assert recovery.cleaned_backup is True
616
+ assert not second_backup.exists()
617
+ assert second_log.read_text(encoding="utf-8") == "larger recovered log"
618
+
619
+
620
+ def test_session_final_progress_merge_preserves_tracker_fatal_error(tmp_path):
621
+ from prizmkit_runtime.sessions import AISessionConfig, AISessionLauncher
622
+
623
+ fake_cli = tmp_path / "context_cli.py"
624
+ fake_cli.write_text(
625
+ "#!/usr/bin/env python3\n"
626
+ "import json\n"
627
+ "print(json.dumps({'type':'result','is_error':True,'result':'API Error: Your input exceeds the context window'}), flush=True)\n",
628
+ encoding="utf-8",
629
+ )
630
+ fake_cli.chmod(0o755)
631
+ prompt = tmp_path / "prompt.md"
632
+ prompt.write_text("hello", encoding="utf-8")
633
+ session_log = tmp_path / "state" / "sessions" / "sess-context" / "logs" / "session.log"
634
+ progress = session_log.with_name("progress.json")
635
+
636
+ result = AISessionLauncher(
637
+ AISessionConfig(
638
+ cli_command=str(fake_cli),
639
+ platform="codebuddy",
640
+ model=None,
641
+ prompt_path=prompt,
642
+ cwd=tmp_path,
643
+ log_path=session_log,
644
+ progress_path=progress,
645
+ )
646
+ ).run()
647
+
648
+ progress_data = json.loads(progress.read_text(encoding="utf-8"))
649
+ assert result.progress_summary.fatal_error_code == "context_overflow"
650
+ assert result.fatal_error_code == "context_overflow"
651
+ assert progress_data["fatal_error_code"] == "context_overflow"
652
+
653
+
654
+ def test_session_unsets_claudecode_and_merges_stderr_order(tmp_path, monkeypatch):
655
+ from prizmkit_runtime.sessions import AISessionConfig, AISessionLauncher
656
+
657
+ fake_cli = tmp_path / "env_cli.py"
658
+ fake_cli.write_text(
659
+ "#!/usr/bin/env python3\n"
660
+ "import os, sys\n"
661
+ "print('stdout-before', flush=True)\n"
662
+ "print('claudecode=' + os.environ.get('CLAUDECODE', '<unset>'), flush=True)\n"
663
+ "print('stderr-after', file=sys.stderr, flush=True)\n",
664
+ encoding="utf-8",
665
+ )
666
+ fake_cli.chmod(0o755)
667
+ monkeypatch.setenv("CLAUDECODE", "1")
668
+ prompt = tmp_path / "prompt.md"
669
+ prompt.write_text("hello", encoding="utf-8")
670
+ session_log = tmp_path / "state" / "sessions" / "sess-env" / "logs" / "session.log"
671
+
672
+ result = AISessionLauncher(
673
+ AISessionConfig(
674
+ cli_command=str(fake_cli),
675
+ platform="codebuddy",
676
+ model=None,
677
+ prompt_path=prompt,
678
+ cwd=tmp_path,
679
+ log_path=session_log,
680
+ )
681
+ ).run()
682
+
683
+ text = session_log.read_text(encoding="utf-8")
684
+ assert result.exit_code == 0
685
+ assert "claudecode=<unset>" in text
686
+ assert text.index("stdout-before") < text.index("stderr-after")
687
+
688
+
689
+ def test_heartbeat_fatal_error_writes_shell_compatible_markers(tmp_path):
690
+ from prizmkit_runtime.heartbeat import HeartbeatConfig, HeartbeatMonitor
691
+ from prizmkit_runtime.processes import ProcessSpec, launch_process
692
+
693
+ sleeper = tmp_path / "fatal_sleep.py"
694
+ sleeper.write_text("import time\ntime.sleep(5)\n", encoding="utf-8")
695
+ progress = tmp_path / "progress.json"
696
+ progress.write_text('{"fatal_error_code":"context_overflow"}\n', encoding="utf-8")
697
+ handle = launch_process(ProcessSpec(command=(sys.executable, str(sleeper)), cwd=tmp_path))
698
+ monitor = HeartbeatMonitor(
699
+ handle,
700
+ HeartbeatConfig(
701
+ heartbeat_file=tmp_path / "heartbeat.json",
702
+ session_log=tmp_path / "session.log",
703
+ progress_file=progress,
704
+ marker_dir=tmp_path,
705
+ interval_seconds=0.05,
706
+ stale_after_seconds=10,
707
+ kill_after_seconds=10,
708
+ grace_seconds=0.1,
709
+ ),
710
+ )
711
+ monitor.start()
712
+ deadline = time.time() + 3
713
+ while time.time() < deadline and not (tmp_path / "fatal-error.json").exists():
714
+ time.sleep(0.05)
715
+ result = monitor.stop()
716
+
717
+ fatal = json.loads((tmp_path / "fatal-error.json").read_text(encoding="utf-8"))
718
+ stale = json.loads((tmp_path / "stale-kill.json").read_text(encoding="utf-8"))
719
+ assert result.killed is True
720
+ assert result.reason == "context_overflow"
721
+ assert fatal["killed_at"].endswith("Z")
722
+ assert fatal["fatal_error_code"] == "context_overflow"
723
+ assert stale["reason"] == "context_overflow"
724
+
725
+
726
+
727
+ from prizmkit_runtime.heartbeat import HeartbeatConfig, write_stale_marker
728
+ from prizmkit_runtime.processes import ProcessSpec, platform_termination_strategy, run_process_with_capture
729
+
730
+ sleeper = tmp_path / "sleep.py"
731
+ sleeper.write_text("import time\ntime.sleep(5)\n", encoding="utf-8")
732
+ result = run_process_with_capture(
733
+ ProcessSpec(command=(sys.executable, str(sleeper)), cwd=tmp_path),
734
+ timeout_seconds=0.2,
735
+ grace_seconds=0.1,
736
+ )
737
+
738
+ assert result.timed_out is True
739
+ assert result.termination is not None
740
+ assert result.termination.reason == "timeout"
741
+ assert platform_termination_strategy()
742
+
743
+ marker = write_stale_marker(tmp_path, reason="stale_session", stale_seconds=3, threshold=2)
744
+ marker_data = json.loads(marker.read_text(encoding="utf-8"))
745
+ assert marker_data["reason"] == "stale_session"
746
+ assert marker_data["threshold"] == 2
747
+ assert HeartbeatConfig(heartbeat_file=tmp_path / "heartbeat").interval_seconds == 30
748
+
749
+
750
+ def test_git_branch_worktree_plans_preserve_shell_invariants(tmp_path):
751
+ from prizmkit_runtime.gitops import (
752
+ BranchContext,
753
+ GitCommandResult,
754
+ GitPlanResult,
755
+ WorktreePolicy,
756
+ branch_create_plan,
757
+ branch_ensure_return_plan,
758
+ branch_merge_plan,
759
+ branch_save_wip,
760
+ branch_save_wip_plan,
761
+ worktree_add_plan,
762
+ )
763
+
764
+ context = BranchContext(base_branch="main", working_branch="dev/example", project_root=tmp_path)
765
+ create = branch_create_plan(context)
766
+ merge = branch_merge_plan(context, auto_push=True)
767
+ save_wip = branch_save_wip_plan(context)
768
+ ensure = branch_ensure_return_plan(context)
769
+ worktree = worktree_add_plan(context, WorktreePolicy(True, cleanup_on_success=False, preserve_on_failure=True))
770
+
771
+ assert create.name == "branch_create"
772
+ assert any(command.args[:2] == ("checkout", "-b") for command in create.commands)
773
+ assert any(command.args[:2] == ("stash", "push") for command in merge.commands)
774
+ assert any(command.args[:2] == ("merge", "--ff-only") for command in merge.commands)
775
+ assert any(command.args == ("push",) for command in merge.commands)
776
+ assert save_wip.never_fails is True
777
+ assert ensure.never_fails is True
778
+ assert any(command.args[:2] == ("rebase", "--abort") for command in ensure.commands)
779
+ assert any(command.args[:2] == ("add", "-A") for command in ensure.commands)
780
+ assert any(".*/worktrees/**" in " ".join(command.args) for command in save_wip.commands)
781
+ assert worktree.commands[0].args[:2] == ("worktree", "add")
782
+
783
+ allowed_failure_result = GitPlanResult(
784
+ plan=create,
785
+ results=(
786
+ GitCommandResult(create.commands[0].args, 1, "", "missing"),
787
+ GitCommandResult(create.commands[1].args, 0, "", ""),
788
+ ),
789
+ )
790
+ assert allowed_failure_result.ok is True
791
+
792
+ failed_branch_change = GitPlanResult(
793
+ plan=create,
794
+ results=(
795
+ GitCommandResult(create.commands[0].args, 1, "", "missing"),
796
+ GitCommandResult(create.commands[1].args, 1, "", "bad base"),
797
+ GitCommandResult(create.commands[2].args, 1, "", "missing branch"),
798
+ ),
799
+ )
800
+ assert failed_branch_change.ok is False
801
+
802
+ skipped_wip = branch_save_wip(tmp_path, "dev/example")
803
+ assert skipped_wip.ok is True
804
+ assert skipped_wip.results == ()
805
+
806
+
807
+ def test_worktree_runtime_context_uses_safe_deterministic_path(tmp_path):
808
+ from prizmkit_runtime.gitops import BranchContext, WorktreePolicy, worktree_runtime_context
809
+
810
+ context = BranchContext(base_branch="main", working_branch="dev/F-001-example", project_root=tmp_path / "repo")
811
+ policy = WorktreePolicy(
812
+ use_worktree=True,
813
+ cleanup_on_success=True,
814
+ preserve_on_failure=False,
815
+ worktree_root=tmp_path / "state" / "worktrees",
816
+ )
817
+
818
+ runtime = worktree_runtime_context(context, policy)
819
+
820
+ assert runtime.worktree_root == (tmp_path / "state" / "worktrees").resolve()
821
+ assert runtime.worktree_path == runtime.worktree_root / "dev-F-001-example"
822
+ assert runtime.sentinel_path == runtime.worktree_path / ".prizmkit-worktree"
823
+ assert runtime.is_cleanup_path_safe() is True
824
+
825
+ unsafe = runtime.__class__(
826
+ context=context,
827
+ policy=policy,
828
+ worktree_root=runtime.worktree_root,
829
+ worktree_path=tmp_path / "other" / "dev-F-001-example",
830
+ sentinel_path=tmp_path / "other" / "dev-F-001-example" / ".prizmkit-worktree",
831
+ )
832
+ assert unsafe.is_cleanup_path_safe() is False
833
+
834
+
835
+ def test_linked_worktree_plans_create_branch_without_checkout_and_cleanup_after_merge(tmp_path):
836
+ from prizmkit_runtime.gitops import (
837
+ BranchContext,
838
+ WorktreePolicy,
839
+ linked_worktree_setup_plan,
840
+ worktree_merge_plan,
841
+ worktree_remove_plan,
842
+ worktree_runtime_context,
843
+ )
844
+
845
+ context = BranchContext(base_branch="main", working_branch="dev/F-001-example", project_root=tmp_path / "repo")
846
+ policy = WorktreePolicy(True, cleanup_on_success=True, preserve_on_failure=False, worktree_root=tmp_path / "worktrees")
847
+ runtime = worktree_runtime_context(context, policy)
848
+
849
+ setup = linked_worktree_setup_plan(runtime)
850
+ merge = worktree_merge_plan(runtime, auto_push=True)
851
+ remove = worktree_remove_plan(runtime, delete_branch=True)
852
+
853
+ assert setup.name == "linked_worktree_setup"
854
+ assert setup.commands[0].args[:2] == ("rev-parse", "--verify")
855
+ assert setup.commands[1].args[:4] == ("branch", context.working_branch, context.base_branch,)
856
+ assert setup.commands[2].args[:2] == ("worktree", "list")
857
+ assert setup.commands[3].args[:3] == ("worktree", "add", "--checkout")
858
+ assert all(command.args[0] != "checkout" for command in setup.commands)
859
+ assert setup.require_one_success == ((0, 1),)
860
+
861
+ assert merge.commands[0].args[:2] == ("rebase", context.base_branch)
862
+ assert merge.commands[1].args == ("merge", "--ff-only", context.working_branch)
863
+ assert merge.commands[2].args == ("push",)
864
+ assert remove.commands[0].args[:2] == ("worktree", "remove")
865
+ assert remove.commands[1].args == ("branch", "-d", context.working_branch)
866
+
867
+
868
+ def test_guarded_worktree_removal_refuses_unregistered_or_unsafe_paths(monkeypatch, tmp_path):
869
+ from prizmkit_runtime.gitops import (
870
+ BranchContext,
871
+ GitCommandResult,
872
+ WorktreePolicy,
873
+ guarded_worktree_remove,
874
+ worktree_runtime_context,
875
+ )
876
+
877
+ context = BranchContext(base_branch="main", working_branch="dev/F-001-example", project_root=tmp_path / "repo")
878
+ runtime = worktree_runtime_context(
879
+ context,
880
+ WorktreePolicy(True, cleanup_on_success=True, preserve_on_failure=False, worktree_root=tmp_path / "worktrees"),
881
+ )
882
+ runtime.worktree_path.mkdir(parents=True)
883
+
884
+ calls = []
885
+
886
+ def fake_run(project_root, args):
887
+ calls.append(tuple(args))
888
+ if tuple(args)[:2] == ("worktree", "list"):
889
+ return GitCommandResult(tuple(args), 0, "", "")
890
+ return GitCommandResult(tuple(args), 0, "", "")
891
+
892
+ monkeypatch.setattr("prizmkit_runtime.gitops.run_git_command", fake_run)
893
+ result = guarded_worktree_remove(runtime, delete_branch=True)
894
+
895
+ assert result.ok is False
896
+ assert result.reason == "missing_worktree_sentinel"
897
+ assert all(call[:2] != ("worktree", "remove") for call in calls)
898
+
899
+ (runtime.sentinel_path).write_text(context.working_branch, encoding="utf-8")
900
+ calls.clear()
901
+ result = guarded_worktree_remove(runtime, delete_branch=True)
902
+
903
+ assert result.ok is False
904
+ assert result.reason == "worktree_not_registered"
905
+ assert all(call[:2] != ("worktree", "remove") for call in calls)
906
+ calls.clear()
907
+
908
+ def registered_run(project_root, args):
909
+ calls.append(tuple(args))
910
+ if tuple(args)[:2] == ("worktree", "list"):
911
+ return GitCommandResult(tuple(args), 0, str(runtime.worktree_path) + " abc [dev/F-001-example]\n", "")
912
+ return GitCommandResult(tuple(args), 0, "", "")
913
+
914
+ monkeypatch.setattr("prizmkit_runtime.gitops.run_git_command", registered_run)
915
+ result = guarded_worktree_remove(runtime, delete_branch=True)
916
+
917
+ assert result.ok is True
918
+ assert ("worktree", "remove", "--force", str(runtime.worktree_path)) in calls
919
+ assert ("branch", "-d", context.working_branch) in calls
920
+
921
+
922
+
923
+ def test_git_status_safe_ignores_worktree_support_assets(monkeypatch, tmp_path):
924
+ from prizmkit_runtime.gitops import HIDDEN_TOOL_WORKTREE_EXCLUDES, git_status_safe
925
+
926
+ captured = []
927
+
928
+ def fake_run(project_root, args):
929
+ captured.append(tuple(args))
930
+ return type("Result", (), {"return_code": 0, "stdout": ""})()
931
+
932
+ monkeypatch.setattr("prizmkit_runtime.gitops.run_git_command", fake_run)
933
+
934
+ assert git_status_safe(tmp_path) == ""
935
+ status_args = captured[0]
936
+ joined = " ".join(status_args)
937
+ assert ":(top,exclude).prizmkit-worktree" in status_args
938
+ assert ":(top,exclude).claude" in status_args
939
+ assert ":(top,exclude).prizmkit/dev-pipeline/scripts" in status_args
940
+ assert all(exclude in status_args for exclude in HIDDEN_TOOL_WORKTREE_EXCLUDES)
941
+ assert ".prizmkit-worktree" in joined
942
+
943
+
944
+ def _init_temp_repo(repo):
945
+ repo.mkdir(parents=True, exist_ok=True)
946
+ subprocess.run(["git", "init", "-b", "main"], cwd=repo, check=True, stdout=subprocess.DEVNULL)
947
+ subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=repo, check=True)
948
+ subprocess.run(["git", "config", "user.name", "Test"], cwd=repo, check=True)
949
+ (repo / "base.txt").write_text("base\n", encoding="utf-8")
950
+ subprocess.run(["git", "add", "base.txt"], cwd=repo, check=True)
951
+ subprocess.run(["git", "commit", "-m", "base"], cwd=repo, check=True, stdout=subprocess.DEVNULL)
952
+
953
+
954
+ def test_materialize_worktree_support_assets_copies_ignored_assets_without_retired_shell_sources(tmp_path):
955
+ from prizmkit_runtime.gitops import (
956
+ BranchContext,
957
+ WorktreePolicy,
958
+ ensure_linked_worktree,
959
+ guarded_worktree_remove,
960
+ materialize_worktree_support_assets,
961
+ worktree_runtime_context,
962
+ )
963
+
964
+ repo = tmp_path / "repo"
965
+ _init_temp_repo(repo)
966
+ (repo / ".gitignore").write_text(".claude/\n.prizmkit/\n", encoding="utf-8")
967
+ subprocess.run(["git", "add", ".gitignore"], cwd=repo, check=True)
968
+ subprocess.run(["git", "commit", "-m", "ignore support"], cwd=repo, check=True, stdout=subprocess.DEVNULL)
969
+
970
+ (repo / ".claude" / "agents").mkdir(parents=True)
971
+ (repo / ".claude" / "agents" / "prizm-dev-team-dev.md").write_text("dev", encoding="utf-8")
972
+ (repo / ".prizmkit" / "prizm-docs").mkdir(parents=True)
973
+ (repo / ".prizmkit" / "prizm-docs" / "root.prizm").write_text("PRIZM_VERSION: 1\n", encoding="utf-8")
974
+ (repo / ".prizmkit" / "config.json").write_text("{}", encoding="utf-8")
975
+ (repo / ".prizmkit" / "manifest.json").write_text("{}", encoding="utf-8")
976
+ (repo / ".prizmkit" / "dev-pipeline" / "scripts").mkdir(parents=True)
977
+ (repo / ".prizmkit" / "dev-pipeline" / "scripts" / "update-checkpoint.py").write_text("# update", encoding="utf-8")
978
+ (repo / ".prizmkit" / "dev-pipeline" / "run-feature.sh").write_text("retired", encoding="utf-8")
979
+
980
+ runtime = worktree_runtime_context(
981
+ BranchContext("main", "dev/support-assets", repo),
982
+ WorktreePolicy(True, cleanup_on_success=True, preserve_on_failure=True, worktree_root=tmp_path / "worktrees"),
983
+ )
984
+ setup = ensure_linked_worktree(runtime)
985
+ assert setup.ok
986
+ assert not (runtime.worktree_path / ".claude" / "agents" / "prizm-dev-team-dev.md").exists()
987
+
988
+ result = materialize_worktree_support_assets(runtime)
989
+
990
+ assert result.ok
991
+ assert (runtime.worktree_path / ".claude" / "agents" / "prizm-dev-team-dev.md").is_file()
992
+ assert (runtime.worktree_path / ".prizmkit" / "prizm-docs" / "root.prizm").is_file()
993
+ assert (runtime.worktree_path / ".prizmkit" / "config.json").is_file()
994
+ assert (runtime.worktree_path / ".prizmkit" / "manifest.json").is_file()
995
+ assert (runtime.worktree_path / ".prizmkit" / "dev-pipeline" / "scripts" / "update-checkpoint.py").is_file()
996
+ assert not (runtime.worktree_path / ".prizmkit" / "dev-pipeline" / "run-feature.sh").exists()
997
+
998
+ assert guarded_worktree_remove(runtime, delete_branch=False).ok
999
+
1000
+
1001
+ def test_worktree_terminal_failure_wip_commit_preserves_dirty_changes_before_cleanup(tmp_path):
1002
+ from prizmkit_runtime.gitops import (
1003
+ BranchContext,
1004
+ WorktreePolicy,
1005
+ ensure_linked_worktree,
1006
+ guarded_worktree_remove,
1007
+ worktree_runtime_context,
1008
+ worktree_save_wip,
1009
+ )
1010
+
1011
+ repo = tmp_path / "repo"
1012
+ _init_temp_repo(repo)
1013
+ runtime = worktree_runtime_context(
1014
+ BranchContext("main", "dev/crashed-work", repo),
1015
+ WorktreePolicy(True, cleanup_on_success=True, preserve_on_failure=True, worktree_root=tmp_path / "worktrees"),
1016
+ )
1017
+ setup = ensure_linked_worktree(runtime)
1018
+ assert setup.ok
1019
+
1020
+ (runtime.worktree_path / "dirty.txt").write_text("dirty work\n", encoding="utf-8")
1021
+ save = worktree_save_wip(runtime)
1022
+ assert save.ok
1023
+ assert any(result.args and result.args[0] == "commit" and result.return_code == 0 for result in save.results)
1024
+
1025
+ removal = guarded_worktree_remove(runtime, delete_branch=False)
1026
+ assert removal.ok
1027
+ assert not runtime.worktree_path.exists()
1028
+
1029
+ branches = subprocess.run(["git", "branch", "--list", "dev/crashed-work"], cwd=repo, check=True, stdout=subprocess.PIPE, text=True).stdout
1030
+ assert "dev/crashed-work" in branches
1031
+ show = subprocess.run(["git", "show", "dev/crashed-work:dirty.txt"], cwd=repo, check=True, stdout=subprocess.PIPE, text=True).stdout
1032
+ assert show == "dirty work\n"
1033
+
1034
+
1035
+ def test_daemon_status_cli_preserves_pure_json_stdout(tmp_path):
1036
+ project = tmp_path / "project"
1037
+ (project / ".prizmkit" / "state" / "features").mkdir(parents=True)
1038
+ result = run_cli("--project-root", str(project), "daemon", "feature", "status")
1039
+
1040
+ assert result.returncode == 1
1041
+ assert json.loads(result.stdout) == {"running": False}
1042
+ assert "Pipeline is not running" in result.stderr
1043
+ assert "Python feature daemon" not in result.stdout
1044
+ assert "Transition command contract" not in result.stdout
1045
+