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.
- package/bin/create-prizmkit.js +4 -4
- package/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/.env.example +3 -3
- package/bundled/dev-pipeline/README.md +110 -90
- package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +1 -1
- package/bundled/dev-pipeline/cli.py +27 -0
- package/bundled/dev-pipeline/prizmkit_runtime/__init__.py +13 -0
- package/bundled/dev-pipeline/prizmkit_runtime/__main__.py +9 -0
- package/bundled/dev-pipeline/prizmkit_runtime/cli.py +193 -0
- package/bundled/dev-pipeline/prizmkit_runtime/commands.py +313 -0
- package/bundled/dev-pipeline/prizmkit_runtime/compat.py +32 -0
- package/bundled/dev-pipeline/prizmkit_runtime/config.py +410 -0
- package/bundled/dev-pipeline/prizmkit_runtime/daemon.py +478 -0
- package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +734 -0
- package/bundled/dev-pipeline/prizmkit_runtime/heartbeat.py +256 -0
- package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +109 -0
- package/bundled/dev-pipeline/prizmkit_runtime/paths.py +110 -0
- package/bundled/dev-pipeline/prizmkit_runtime/processes.py +373 -0
- package/bundled/dev-pipeline/prizmkit_runtime/reset.py +389 -0
- package/bundled/dev-pipeline/prizmkit_runtime/runner_bookkeeping.py +51 -0
- package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +179 -0
- package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +345 -0
- package/bundled/dev-pipeline/prizmkit_runtime/runner_prompts.py +118 -0
- package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +187 -0
- package/bundled/dev-pipeline/prizmkit_runtime/runner_status.py +147 -0
- package/bundled/dev-pipeline/prizmkit_runtime/runners.py +422 -0
- package/bundled/dev-pipeline/prizmkit_runtime/sessions.py +552 -0
- package/bundled/dev-pipeline/prizmkit_runtime/status.py +71 -0
- package/bundled/dev-pipeline/scripts/check-session-status.py +74 -1
- package/bundled/dev-pipeline/scripts/continuation.py +374 -0
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +64 -52
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +35 -25
- package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +2 -2
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +15 -20
- package/bundled/dev-pipeline/scripts/parse-stream-progress.py +185 -16
- package/bundled/dev-pipeline/scripts/update-bug-status.py +214 -6
- package/bundled/dev-pipeline/scripts/update-feature-status.py +237 -6
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +214 -6
- package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +8 -2
- package/bundled/dev-pipeline/templates/bootstrap-prompt.md +4 -0
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +14 -5
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +16 -6
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +16 -6
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +1 -1
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +3 -3
- package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +2 -0
- package/bundled/dev-pipeline/templates/sections/log-size-awareness.md +13 -69
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +15 -7
- package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +42 -29
- package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +41 -39
- package/bundled/dev-pipeline/templates/sections/test-failure-recovery-agent.md +4 -0
- package/bundled/dev-pipeline/templates/sections/test-failure-recovery-lite.md +4 -0
- package/bundled/dev-pipeline/templates/session-status-schema.json +1 -1
- package/bundled/dev-pipeline/tests/conftest.py +1 -0
- package/bundled/dev-pipeline/tests/test_auto_skip.py +932 -0
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +207 -0
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +100 -0
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +43 -0
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +1138 -0
- package/bundled/dev-pipeline/tests/test_unified_cli.py +1045 -0
- package/bundled/skills/_metadata.json +1 -1
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +29 -34
- package/bundled/skills/bugfix-pipeline-launcher/references/configuration.md +6 -5
- package/bundled/skills/feature-pipeline-launcher/SKILL.md +29 -35
- package/bundled/skills/feature-pipeline-launcher/references/configuration.md +7 -6
- package/bundled/skills/feature-workflow/SKILL.md +3 -6
- package/bundled/skills/recovery-workflow/SKILL.md +23 -32
- package/bundled/skills/refactor-pipeline-launcher/SKILL.md +29 -35
- package/bundled/skills/refactor-pipeline-launcher/references/configuration.md +7 -6
- package/bundled/skills/refactor-workflow/SKILL.md +3 -6
- package/bundled/templates/hooks/commit-intent.json +2 -2
- package/bundled/templates/hooks/diff-prizm-docs.py +226 -0
- package/bundled/templates/hooks/prizm-pre-commit.py +45 -0
- package/bundled/templates/hooks/validate-prizm-docs.py +240 -0
- package/package.json +1 -1
- package/src/config.js +33 -12
- package/src/index.js +3 -6
- package/src/manifest.js +3 -2
- package/src/metadata.js +7 -18
- package/src/prompts.js +0 -12
- package/src/runtimes.js +18 -11
- package/src/scaffold.js +133 -116
- package/src/upgrade.js +1 -1
- package/bundled/dev-pipeline/launch-bugfix-daemon.sh +0 -500
- package/bundled/dev-pipeline/launch-feature-daemon.sh +0 -657
- package/bundled/dev-pipeline/launch-refactor-daemon.sh +0 -502
- package/bundled/dev-pipeline/lib/branch.sh +0 -326
- package/bundled/dev-pipeline/lib/common.sh +0 -1257
- package/bundled/dev-pipeline/lib/heartbeat.sh +0 -469
- package/bundled/dev-pipeline/reset-bug.sh +0 -395
- package/bundled/dev-pipeline/reset-feature.sh +0 -406
- package/bundled/dev-pipeline/reset-refactor.sh +0 -393
- package/bundled/dev-pipeline/run-bugfix.sh +0 -1315
- package/bundled/dev-pipeline/run-feature.sh +0 -1602
- package/bundled/dev-pipeline/run-recovery.sh +0 -648
- package/bundled/dev-pipeline/run-refactor.sh +0 -1348
- package/bundled/dev-pipeline/scripts/monitor-log.sh +0 -104
- package/bundled/dev-pipeline/tests/test-path-resolution.sh +0 -100
- package/bundled/dev-pipeline-windows/.env.example +0 -36
- package/bundled/dev-pipeline-windows/README.md +0 -30
- package/bundled/dev-pipeline-windows/SCHEMA_ANALYSIS.md +0 -533
- package/bundled/dev-pipeline-windows/assets/feature-list-example.json +0 -146
- package/bundled/dev-pipeline-windows/assets/prizm-dev-team-integration.md +0 -137
- package/bundled/dev-pipeline-windows/launch-bugfix-daemon.ps1 +0 -9
- package/bundled/dev-pipeline-windows/launch-feature-daemon.ps1 +0 -9
- package/bundled/dev-pipeline-windows/launch-refactor-daemon.ps1 +0 -9
- package/bundled/dev-pipeline-windows/lib/branch.ps1 +0 -235
- package/bundled/dev-pipeline-windows/lib/common.ps1 +0 -829
- package/bundled/dev-pipeline-windows/lib/daemon.ps1 +0 -140
- package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +0 -967
- package/bundled/dev-pipeline-windows/lib/reset.ps1 +0 -87
- package/bundled/dev-pipeline-windows/reset-bug.ps1 +0 -9
- package/bundled/dev-pipeline-windows/reset-feature.ps1 +0 -9
- package/bundled/dev-pipeline-windows/reset-refactor.ps1 +0 -9
- package/bundled/dev-pipeline-windows/run-bugfix.ps1 +0 -9
- package/bundled/dev-pipeline-windows/run-feature.ps1 +0 -9
- package/bundled/dev-pipeline-windows/run-recovery.ps1 +0 -179
- package/bundled/dev-pipeline-windows/run-refactor.ps1 +0 -9
- package/bundled/dev-pipeline-windows/scripts/check-session-status.py +0 -228
- package/bundled/dev-pipeline-windows/scripts/detect-stuck.py +0 -530
- package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +0 -2282
- package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +0 -767
- package/bundled/dev-pipeline-windows/scripts/generate-recovery-prompt.py +0 -805
- package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +0 -845
- package/bundled/dev-pipeline-windows/scripts/init-bugfix-pipeline.py +0 -316
- package/bundled/dev-pipeline-windows/scripts/init-dev-team.py +0 -134
- package/bundled/dev-pipeline-windows/scripts/init-pipeline.py +0 -382
- package/bundled/dev-pipeline-windows/scripts/init-refactor-pipeline.py +0 -399
- package/bundled/dev-pipeline-windows/scripts/monitor-log.ps1 +0 -102
- package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +0 -1232
- package/bundled/dev-pipeline-windows/scripts/patch-completion-notes.py +0 -191
- package/bundled/dev-pipeline-windows/scripts/prizmkit-test-gate.py +0 -446
- package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +0 -913
- package/bundled/dev-pipeline-windows/scripts/update-checkpoint.py +0 -173
- package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +0 -1558
- package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +0 -1119
- package/bundled/dev-pipeline-windows/scripts/utils.py +0 -542
- package/bundled/dev-pipeline-windows/templates/agent-prompts/critic-plan-challenge.md +0 -7
- package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-fix.md +0 -7
- package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-implement.md +0 -65
- package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-resume.md +0 -5
- package/bundled/dev-pipeline-windows/templates/agent-prompts/reviewer-review.md +0 -7
- package/bundled/dev-pipeline-windows/templates/bootstrap-prompt.md +0 -73
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +0 -589
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +0 -710
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +0 -791
- package/bundled/dev-pipeline-windows/templates/bug-fix-list-schema.json +0 -263
- package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +0 -326
- package/bundled/dev-pipeline-windows/templates/feature-list-schema.json +0 -237
- package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +0 -340
- package/bundled/dev-pipeline-windows/templates/refactor-list-schema.json +0 -270
- package/bundled/dev-pipeline-windows/templates/sections/ac-verification-checklist.md +0 -7
- package/bundled/dev-pipeline-windows/templates/sections/checkpoint-system.md +0 -91
- package/bundled/dev-pipeline-windows/templates/sections/context-budget-rules.md +0 -34
- package/bundled/dev-pipeline-windows/templates/sections/critical-paths-agent.md +0 -10
- package/bundled/dev-pipeline-windows/templates/sections/critical-paths-full.md +0 -12
- package/bundled/dev-pipeline-windows/templates/sections/critical-paths-lite.md +0 -7
- package/bundled/dev-pipeline-windows/templates/sections/directory-convention-agent.md +0 -8
- package/bundled/dev-pipeline-windows/templates/sections/directory-convention-full.md +0 -9
- package/bundled/dev-pipeline-windows/templates/sections/directory-convention-lite.md +0 -6
- package/bundled/dev-pipeline-windows/templates/sections/failure-capture.md +0 -21
- package/bundled/dev-pipeline-windows/templates/sections/feature-context.md +0 -21
- package/bundled/dev-pipeline-windows/templates/sections/log-size-awareness.md +0 -78
- package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-auto.md +0 -283
- package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-opencli.md +0 -112
- package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification.md +0 -168
- package/bundled/dev-pipeline-windows/templates/sections/phase-commit-full.md +0 -82
- package/bundled/dev-pipeline-windows/templates/sections/phase-commit.md +0 -75
- package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-agent-suffix.md +0 -23
- package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-base.md +0 -14
- package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-lite-suffix.md +0 -19
- package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan-full.md +0 -63
- package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan.md +0 -42
- package/bundled/dev-pipeline-windows/templates/sections/phase-implement-agent.md +0 -44
- package/bundled/dev-pipeline-windows/templates/sections/phase-implement-full.md +0 -59
- package/bundled/dev-pipeline-windows/templates/sections/phase-implement-lite.md +0 -39
- package/bundled/dev-pipeline-windows/templates/sections/phase-plan-agent.md +0 -27
- package/bundled/dev-pipeline-windows/templates/sections/phase-plan-lite.md +0 -27
- package/bundled/dev-pipeline-windows/templates/sections/phase-prizmkit-test.md +0 -39
- package/bundled/dev-pipeline-windows/templates/sections/phase-review-agent.md +0 -35
- package/bundled/dev-pipeline-windows/templates/sections/phase-review-full.md +0 -35
- package/bundled/dev-pipeline-windows/templates/sections/phase-specify-plan-full.md +0 -73
- package/bundled/dev-pipeline-windows/templates/sections/phase0-init.md +0 -13
- package/bundled/dev-pipeline-windows/templates/sections/phase0-test-baseline.md +0 -21
- package/bundled/dev-pipeline-windows/templates/sections/session-context.md +0 -5
- package/bundled/dev-pipeline-windows/templates/sections/subagent-timeout-recovery.md +0 -6
- package/bundled/dev-pipeline-windows/templates/sections/task-contract.md +0 -34
- package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-agent.md +0 -48
- package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-lite.md +0 -48
- package/bundled/dev-pipeline-windows/templates/session-status-schema.json +0 -83
- package/bundled/skills-windows/app-planner/SKILL.md +0 -296
- package/bundled/skills-windows/app-planner/assets/app-design-guide.md +0 -101
- package/bundled/skills-windows/app-planner/references/architecture-decisions.md +0 -52
- package/bundled/skills-windows/app-planner/references/brainstorm-guide.md +0 -101
- package/bundled/skills-windows/app-planner/references/frontend-design-guide.md +0 -71
- package/bundled/skills-windows/app-planner/references/infrastructure-convention-discovery.md +0 -108
- package/bundled/skills-windows/app-planner/references/project-brief-guide.md +0 -82
- package/bundled/skills-windows/app-planner/references/project-conventions-discovery.md +0 -59
- package/bundled/skills-windows/app-planner/references/project-state-detection.md +0 -90
- package/bundled/skills-windows/app-planner/references/red-team-checklist.md +0 -40
- package/bundled/skills-windows/app-planner/references/rules/backend/derivation-rules.md +0 -619
- package/bundled/skills-windows/app-planner/references/rules/backend/fixed-rules.md +0 -285
- package/bundled/skills-windows/app-planner/references/rules/backend/question-bank.md +0 -249
- package/bundled/skills-windows/app-planner/references/rules/backend/question-manifest.json +0 -46
- package/bundled/skills-windows/app-planner/references/rules/backend/template.md +0 -173
- package/bundled/skills-windows/app-planner/references/rules/database/derivation-rules.md +0 -382
- package/bundled/skills-windows/app-planner/references/rules/database/fixed-rules.md +0 -211
- package/bundled/skills-windows/app-planner/references/rules/database/question-bank.md +0 -184
- package/bundled/skills-windows/app-planner/references/rules/database/question-manifest.json +0 -39
- package/bundled/skills-windows/app-planner/references/rules/database/template.md +0 -158
- package/bundled/skills-windows/app-planner/references/rules/frontend/derivation-rules.md +0 -820
- package/bundled/skills-windows/app-planner/references/rules/frontend/fixed-rules.md +0 -188
- package/bundled/skills-windows/app-planner/references/rules/frontend/question-bank.md +0 -319
- package/bundled/skills-windows/app-planner/references/rules/frontend/question-manifest.json +0 -51
- package/bundled/skills-windows/app-planner/references/rules/frontend/template.md +0 -339
- package/bundled/skills-windows/app-planner/references/rules/mobile/derivation-rules.md +0 -649
- package/bundled/skills-windows/app-planner/references/rules/mobile/fixed-rules.md +0 -290
- package/bundled/skills-windows/app-planner/references/rules/mobile/question-bank.md +0 -232
- package/bundled/skills-windows/app-planner/references/rules/mobile/question-manifest.json +0 -47
- package/bundled/skills-windows/app-planner/references/rules/mobile/template.md +0 -175
- package/bundled/skills-windows/app-planner/references/rules-configuration.md +0 -46
- package/bundled/skills-windows/bug-fix-workflow/SKILL.md +0 -379
- package/bundled/skills-windows/bug-fix-workflow/references/bug-diagnosis.md +0 -41
- package/bundled/skills-windows/bug-planner/SKILL.md +0 -402
- package/bundled/skills-windows/bug-planner/assets/bug-confirmation-template.md +0 -43
- package/bundled/skills-windows/bug-planner/references/critic-and-verification.md +0 -44
- package/bundled/skills-windows/bug-planner/references/error-recovery.md +0 -73
- package/bundled/skills-windows/bug-planner/references/input-formats.md +0 -53
- package/bundled/skills-windows/bug-planner/references/schema-validation.md +0 -25
- package/bundled/skills-windows/bug-planner/references/severity-rules.md +0 -16
- package/bundled/skills-windows/bug-planner/scripts/validate-bug-list.py +0 -322
- package/bundled/skills-windows/bugfix-pipeline-launcher/SKILL.md +0 -304
- package/bundled/skills-windows/bugfix-pipeline-launcher/references/configuration.md +0 -84
- package/bundled/skills-windows/feature-pipeline-launcher/SKILL.md +0 -381
- package/bundled/skills-windows/feature-pipeline-launcher/references/configuration.md +0 -67
- package/bundled/skills-windows/feature-pipeline-launcher/scripts/preflight-check.py +0 -462
- package/bundled/skills-windows/feature-planner/SKILL.md +0 -397
- package/bundled/skills-windows/feature-planner/assets/evaluation-guide.md +0 -64
- package/bundled/skills-windows/feature-planner/assets/planning-guide.md +0 -214
- package/bundled/skills-windows/feature-planner/references/browser-interaction.md +0 -59
- package/bundled/skills-windows/feature-planner/references/completeness-review.md +0 -57
- package/bundled/skills-windows/feature-planner/references/decomposition-patterns.md +0 -75
- package/bundled/skills-windows/feature-planner/references/error-recovery.md +0 -90
- package/bundled/skills-windows/feature-planner/references/incremental-feature-planning.md +0 -112
- package/bundled/skills-windows/feature-planner/references/new-project-planning.md +0 -85
- package/bundled/skills-windows/feature-planner/scripts/validate-and-generate.py +0 -1020
- package/bundled/skills-windows/feature-workflow/SKILL.md +0 -374
- package/bundled/skills-windows/feature-workflow/references/brainstorm-guide.md +0 -137
- package/bundled/skills-windows/prizm-kit/SKILL.md +0 -93
- package/bundled/skills-windows/prizmkit-code-review/SKILL.md +0 -161
- package/bundled/skills-windows/prizmkit-code-review/references/dev-agent-prompt.md +0 -30
- package/bundled/skills-windows/prizmkit-code-review/references/review-report-template.md +0 -31
- package/bundled/skills-windows/prizmkit-code-review/references/reviewer-agent-prompt.md +0 -66
- package/bundled/skills-windows/prizmkit-code-review/scripts/check_loop.py +0 -186
- package/bundled/skills-windows/prizmkit-committer/SKILL.md +0 -89
- package/bundled/skills-windows/prizmkit-deploy/SKILL.md +0 -444
- package/bundled/skills-windows/prizmkit-deploy/references/ci-cd-workflows.md +0 -115
- package/bundled/skills-windows/prizmkit-deploy/references/cloud-platform-deploy.md +0 -93
- package/bundled/skills-windows/prizmkit-deploy/references/data-safety-examples.md +0 -120
- package/bundled/skills-windows/prizmkit-deploy/references/database-setup.md +0 -46
- package/bundled/skills-windows/prizmkit-deploy/references/deploy-config-schema.md +0 -148
- package/bundled/skills-windows/prizmkit-deploy/references/deploy-history-schema.md +0 -62
- package/bundled/skills-windows/prizmkit-deploy/references/deployment-modes.md +0 -50
- package/bundled/skills-windows/prizmkit-deploy/references/direct-upload.md +0 -26
- package/bundled/skills-windows/prizmkit-deploy/references/dns-setup.md +0 -42
- package/bundled/skills-windows/prizmkit-deploy/references/docker-deploy.md +0 -31
- package/bundled/skills-windows/prizmkit-deploy/references/firewall-setup.md +0 -37
- package/bundled/skills-windows/prizmkit-deploy/references/live-validation-notes.md +0 -21
- package/bundled/skills-windows/prizmkit-deploy/references/nginx-blue-green.md +0 -59
- package/bundled/skills-windows/prizmkit-deploy/references/ssh-bootstrap-flow.md +0 -49
- package/bundled/skills-windows/prizmkit-deploy/references/ssh-execution-flow.md +0 -41
- package/bundled/skills-windows/prizmkit-deploy/references/ssh-takeover.md +0 -20
- package/bundled/skills-windows/prizmkit-deploy/references/ssl-setup.md +0 -56
- package/bundled/skills-windows/prizmkit-implement/SKILL.md +0 -71
- package/bundled/skills-windows/prizmkit-init/SKILL.md +0 -356
- package/bundled/skills-windows/prizmkit-init/assets/project-brief-template.md +0 -82
- package/bundled/skills-windows/prizmkit-init/references/config-schema.md +0 -70
- package/bundled/skills-windows/prizmkit-init/references/rules/layer-detection.md +0 -41
- package/bundled/skills-windows/prizmkit-init/references/tech-stack-catalog.md +0 -13
- package/bundled/skills-windows/prizmkit-init/references/update-supplement.md +0 -9
- package/bundled/skills-windows/prizmkit-plan/SKILL.md +0 -102
- package/bundled/skills-windows/prizmkit-plan/assets/plan-template.md +0 -115
- package/bundled/skills-windows/prizmkit-plan/assets/spec-template.md +0 -73
- package/bundled/skills-windows/prizmkit-plan/references/clarify-guide.md +0 -67
- package/bundled/skills-windows/prizmkit-plan/references/examples.md +0 -85
- package/bundled/skills-windows/prizmkit-plan/references/verification-checklist.md +0 -60
- package/bundled/skills-windows/prizmkit-prizm-docs/SKILL.md +0 -129
- package/bundled/skills-windows/prizmkit-prizm-docs/assets/prizm-docs-format.md +0 -613
- package/bundled/skills-windows/prizmkit-prizm-docs/references/op-init.md +0 -45
- package/bundled/skills-windows/prizmkit-prizm-docs/references/op-rebuild.md +0 -15
- package/bundled/skills-windows/prizmkit-prizm-docs/references/op-status.md +0 -14
- package/bundled/skills-windows/prizmkit-prizm-docs/references/op-update.md +0 -19
- package/bundled/skills-windows/prizmkit-prizm-docs/references/op-validate.md +0 -17
- package/bundled/skills-windows/prizmkit-retrospective/SKILL.md +0 -87
- package/bundled/skills-windows/prizmkit-retrospective/references/knowledge-injection-steps.md +0 -50
- package/bundled/skills-windows/prizmkit-retrospective/references/structural-sync-steps.md +0 -43
- package/bundled/skills-windows/prizmkit-test/SKILL.md +0 -227
- package/bundled/skills-windows/prizmkit-test/references/boundary-coverage-protocol.md +0 -220
- package/bundled/skills-windows/prizmkit-test/references/examples.md +0 -143
- package/bundled/skills-windows/prizmkit-test/references/service-boundary-test-catalog.md +0 -225
- package/bundled/skills-windows/prizmkit-test/references/test-generation-steps.md +0 -109
- package/bundled/skills-windows/prizmkit-test/references/test-report-template.md +0 -111
- package/bundled/skills-windows/prizmkit-test/scripts/validate_boundary_report.py +0 -347
- package/bundled/skills-windows/recovery-workflow/SKILL.md +0 -355
- package/bundled/skills-windows/recovery-workflow/evals/evals.json +0 -46
- package/bundled/skills-windows/recovery-workflow/references/detection.md +0 -58
- package/bundled/skills-windows/recovery-workflow/scripts/detect-recovery-state.py +0 -544
- package/bundled/skills-windows/refactor-pipeline-launcher/SKILL.md +0 -346
- package/bundled/skills-windows/refactor-pipeline-launcher/references/configuration.md +0 -72
- package/bundled/skills-windows/refactor-planner/SKILL.md +0 -398
- package/bundled/skills-windows/refactor-planner/assets/planning-guide.md +0 -292
- package/bundled/skills-windows/refactor-planner/references/behavior-preservation.md +0 -301
- package/bundled/skills-windows/refactor-planner/references/fast-path.md +0 -59
- package/bundled/skills-windows/refactor-planner/references/planning-phases.md +0 -135
- package/bundled/skills-windows/refactor-planner/references/refactor-scoping-guide.md +0 -221
- package/bundled/skills-windows/refactor-planner/scripts/validate-and-generate-refactor.py +0 -858
- package/bundled/skills-windows/refactor-workflow/SKILL.md +0 -343
- package/bundled/skills-windows/refactor-workflow/references/brainstorm-guide.md +0 -116
- package/bundled/templates/hooks/diff-prizm-docs.sh +0 -158
- package/bundled/templates/hooks/prizm-pre-commit.sh +0 -108
- 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
|
+
|