prizmkit 1.1.57 → 1.1.60

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 (188) hide show
  1. package/bin/create-prizmkit.js +8 -6
  2. package/bundled/VERSION.json +3 -3
  3. package/bundled/adapters/codex/agent-adapter.js +38 -0
  4. package/bundled/adapters/codex/paths.js +27 -0
  5. package/bundled/adapters/codex/rules-adapter.js +30 -0
  6. package/bundled/adapters/codex/settings-adapter.js +27 -0
  7. package/bundled/adapters/codex/skill-adapter.js +65 -0
  8. package/bundled/adapters/codex/team-adapter.js +37 -0
  9. package/bundled/dev-pipeline/.env.example +2 -1
  10. package/bundled/dev-pipeline/README.md +10 -7
  11. package/bundled/dev-pipeline/lib/common.sh +278 -37
  12. package/bundled/dev-pipeline/run-bugfix.sh +10 -61
  13. package/bundled/dev-pipeline/run-feature.sh +10 -78
  14. package/bundled/dev-pipeline/run-recovery.sh +10 -46
  15. package/bundled/dev-pipeline/run-refactor.sh +10 -61
  16. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +17 -7
  17. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +9 -3
  18. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +9 -3
  19. package/bundled/dev-pipeline/scripts/utils.py +6 -4
  20. package/bundled/dev-pipeline-windows/.env.example +28 -0
  21. package/bundled/dev-pipeline-windows/README.md +30 -0
  22. package/bundled/dev-pipeline-windows/SCHEMA_ANALYSIS.md +525 -0
  23. package/bundled/dev-pipeline-windows/assets/feature-list-example.json +146 -0
  24. package/bundled/dev-pipeline-windows/assets/prizm-dev-team-integration.md +138 -0
  25. package/bundled/dev-pipeline-windows/launch-bugfix-daemon.ps1 +9 -0
  26. package/bundled/dev-pipeline-windows/launch-feature-daemon.ps1 +9 -0
  27. package/bundled/dev-pipeline-windows/launch-refactor-daemon.ps1 +9 -0
  28. package/bundled/dev-pipeline-windows/lib/common.ps1 +432 -0
  29. package/bundled/dev-pipeline-windows/lib/daemon.ps1 +140 -0
  30. package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +446 -0
  31. package/bundled/dev-pipeline-windows/lib/reset.ps1 +87 -0
  32. package/bundled/dev-pipeline-windows/reset-bug.ps1 +9 -0
  33. package/bundled/dev-pipeline-windows/reset-feature.ps1 +9 -0
  34. package/bundled/dev-pipeline-windows/reset-refactor.ps1 +9 -0
  35. package/bundled/dev-pipeline-windows/run-bugfix.ps1 +9 -0
  36. package/bundled/dev-pipeline-windows/run-feature.ps1 +9 -0
  37. package/bundled/dev-pipeline-windows/run-recovery.ps1 +76 -0
  38. package/bundled/dev-pipeline-windows/run-refactor.ps1 +9 -0
  39. package/bundled/dev-pipeline-windows/scripts/check-session-status.py +228 -0
  40. package/bundled/dev-pipeline-windows/scripts/cleanup-logs.py +192 -0
  41. package/bundled/dev-pipeline-windows/scripts/detect-stuck.py +530 -0
  42. package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +1737 -0
  43. package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +685 -0
  44. package/bundled/dev-pipeline-windows/scripts/generate-recovery-prompt.py +805 -0
  45. package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +763 -0
  46. package/bundled/dev-pipeline-windows/scripts/init-bugfix-pipeline.py +316 -0
  47. package/bundled/dev-pipeline-windows/scripts/init-dev-team.py +134 -0
  48. package/bundled/dev-pipeline-windows/scripts/init-pipeline.py +380 -0
  49. package/bundled/dev-pipeline-windows/scripts/init-refactor-pipeline.py +399 -0
  50. package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +388 -0
  51. package/bundled/dev-pipeline-windows/scripts/patch-completion-notes.py +191 -0
  52. package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +864 -0
  53. package/bundled/dev-pipeline-windows/scripts/update-checkpoint.py +173 -0
  54. package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +1501 -0
  55. package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +1073 -0
  56. package/bundled/dev-pipeline-windows/scripts/utils.py +542 -0
  57. package/bundled/dev-pipeline-windows/templates/agent-prompts/critic-plan-challenge.md +7 -0
  58. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-fix.md +7 -0
  59. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-implement.md +30 -0
  60. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-resume.md +5 -0
  61. package/bundled/dev-pipeline-windows/templates/agent-prompts/reviewer-review.md +7 -0
  62. package/bundled/dev-pipeline-windows/templates/bootstrap-prompt.md +46 -0
  63. package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +43 -0
  64. package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +43 -0
  65. package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +43 -0
  66. package/bundled/dev-pipeline-windows/templates/bug-fix-list-schema.json +263 -0
  67. package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +320 -0
  68. package/bundled/dev-pipeline-windows/templates/feature-list-schema.json +237 -0
  69. package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +331 -0
  70. package/bundled/dev-pipeline-windows/templates/refactor-list-schema.json +270 -0
  71. package/bundled/dev-pipeline-windows/templates/sections/ac-verification-checklist.md +13 -0
  72. package/bundled/dev-pipeline-windows/templates/sections/checkpoint-system.md +91 -0
  73. package/bundled/dev-pipeline-windows/templates/sections/context-budget-rules.md +33 -0
  74. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-agent.md +10 -0
  75. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-full.md +12 -0
  76. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-lite.md +7 -0
  77. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-agent.md +8 -0
  78. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-full.md +9 -0
  79. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-lite.md +6 -0
  80. package/bundled/dev-pipeline-windows/templates/sections/failure-capture.md +21 -0
  81. package/bundled/dev-pipeline-windows/templates/sections/feature-context.md +31 -0
  82. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-auto.md +72 -0
  83. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-opencli.md +63 -0
  84. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification.md +62 -0
  85. package/bundled/dev-pipeline-windows/templates/sections/phase-commit-full.md +71 -0
  86. package/bundled/dev-pipeline-windows/templates/sections/phase-commit.md +64 -0
  87. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-agent-suffix.md +23 -0
  88. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-base.md +24 -0
  89. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-lite-suffix.md +12 -0
  90. package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan-full.md +53 -0
  91. package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan.md +32 -0
  92. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-agent.md +37 -0
  93. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-full.md +50 -0
  94. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-lite.md +52 -0
  95. package/bundled/dev-pipeline-windows/templates/sections/phase-plan-agent.md +27 -0
  96. package/bundled/dev-pipeline-windows/templates/sections/phase-plan-lite.md +27 -0
  97. package/bundled/dev-pipeline-windows/templates/sections/phase-review-agent.md +27 -0
  98. package/bundled/dev-pipeline-windows/templates/sections/phase-review-full.md +29 -0
  99. package/bundled/dev-pipeline-windows/templates/sections/phase-specify-plan-full.md +77 -0
  100. package/bundled/dev-pipeline-windows/templates/sections/phase0-init.md +13 -0
  101. package/bundled/dev-pipeline-windows/templates/sections/phase0-test-baseline.md +23 -0
  102. package/bundled/dev-pipeline-windows/templates/sections/session-context.md +5 -0
  103. package/bundled/dev-pipeline-windows/templates/sections/subagent-timeout-recovery.md +6 -0
  104. package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-agent.md +67 -0
  105. package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-lite.md +58 -0
  106. package/bundled/dev-pipeline-windows/templates/session-status-schema.json +83 -0
  107. package/bundled/skills/_metadata.json +1 -1
  108. package/bundled/skills/app-planner/SKILL.md +26 -18
  109. package/bundled/skills/app-planner/references/architecture-decisions.md +9 -5
  110. package/bundled/skills/app-planner/references/frontend-design-guide.md +1 -1
  111. package/bundled/skills/feature-planner/SKILL.md +9 -2
  112. package/bundled/skills/prizmkit-init/SKILL.md +7 -6
  113. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +2 -0
  114. package/bundled/skills-windows/app-planner/SKILL.md +639 -0
  115. package/bundled/skills-windows/app-planner/assets/app-design-guide.md +101 -0
  116. package/bundled/skills-windows/app-planner/references/architecture-decisions.md +52 -0
  117. package/bundled/skills-windows/app-planner/references/brainstorm-guide.md +101 -0
  118. package/bundled/skills-windows/app-planner/references/frontend-design-guide.md +71 -0
  119. package/bundled/skills-windows/app-planner/references/project-brief-guide.md +82 -0
  120. package/bundled/skills-windows/app-planner/references/red-team-checklist.md +40 -0
  121. package/bundled/skills-windows/app-planner/references/rules/backend/derivation-rules.md +609 -0
  122. package/bundled/skills-windows/app-planner/references/rules/backend/fixed-rules.md +285 -0
  123. package/bundled/skills-windows/app-planner/references/rules/backend/question-bank.md +249 -0
  124. package/bundled/skills-windows/app-planner/references/rules/backend/template.md +173 -0
  125. package/bundled/skills-windows/app-planner/references/rules/database/derivation-rules.md +373 -0
  126. package/bundled/skills-windows/app-planner/references/rules/database/fixed-rules.md +211 -0
  127. package/bundled/skills-windows/app-planner/references/rules/database/question-bank.md +184 -0
  128. package/bundled/skills-windows/app-planner/references/rules/database/template.md +158 -0
  129. package/bundled/skills-windows/app-planner/references/rules/frontend/derivation-rules.md +810 -0
  130. package/bundled/skills-windows/app-planner/references/rules/frontend/fixed-rules.md +188 -0
  131. package/bundled/skills-windows/app-planner/references/rules/frontend/question-bank.md +302 -0
  132. package/bundled/skills-windows/app-planner/references/rules/frontend/template.md +320 -0
  133. package/bundled/skills-windows/app-planner/references/rules/mobile/derivation-rules.md +639 -0
  134. package/bundled/skills-windows/app-planner/references/rules/mobile/fixed-rules.md +290 -0
  135. package/bundled/skills-windows/app-planner/references/rules/mobile/question-bank.md +232 -0
  136. package/bundled/skills-windows/app-planner/references/rules/mobile/template.md +175 -0
  137. package/bundled/skills-windows/bug-fix-workflow/SKILL.md +415 -0
  138. package/bundled/skills-windows/bug-planner/SKILL.md +395 -0
  139. package/bundled/skills-windows/bug-planner/assets/bug-confirmation-template.md +43 -0
  140. package/bundled/skills-windows/bug-planner/references/critic-and-verification.md +44 -0
  141. package/bundled/skills-windows/bug-planner/references/error-recovery.md +73 -0
  142. package/bundled/skills-windows/bug-planner/references/input-formats.md +53 -0
  143. package/bundled/skills-windows/bug-planner/references/schema-validation.md +25 -0
  144. package/bundled/skills-windows/bug-planner/references/severity-rules.md +16 -0
  145. package/bundled/skills-windows/bug-planner/scripts/validate-bug-list.py +322 -0
  146. package/bundled/skills-windows/bugfix-pipeline-launcher/SKILL.md +380 -0
  147. package/bundled/skills-windows/feature-pipeline-launcher/SKILL.md +441 -0
  148. package/bundled/skills-windows/feature-pipeline-launcher/scripts/preflight-check.py +462 -0
  149. package/bundled/skills-windows/feature-planner/SKILL.md +401 -0
  150. package/bundled/skills-windows/feature-planner/assets/evaluation-guide.md +64 -0
  151. package/bundled/skills-windows/feature-planner/assets/planning-guide.md +214 -0
  152. package/bundled/skills-windows/feature-planner/references/browser-interaction.md +59 -0
  153. package/bundled/skills-windows/feature-planner/references/completeness-review.md +57 -0
  154. package/bundled/skills-windows/feature-planner/references/decomposition-patterns.md +75 -0
  155. package/bundled/skills-windows/feature-planner/references/error-recovery.md +90 -0
  156. package/bundled/skills-windows/feature-planner/references/incremental-feature-planning.md +112 -0
  157. package/bundled/skills-windows/feature-planner/references/new-project-planning.md +85 -0
  158. package/bundled/skills-windows/feature-planner/scripts/validate-and-generate.py +1029 -0
  159. package/bundled/skills-windows/feature-workflow/SKILL.md +531 -0
  160. package/bundled/skills-windows/prizmkit-init/SKILL.md +356 -0
  161. package/bundled/skills-windows/prizmkit-init/assets/project-brief-template.md +82 -0
  162. package/bundled/skills-windows/prizmkit-init/references/config-schema.md +68 -0
  163. package/bundled/skills-windows/prizmkit-init/references/rules/layer-detection.md +41 -0
  164. package/bundled/skills-windows/prizmkit-init/references/tech-stack-catalog.md +13 -0
  165. package/bundled/skills-windows/prizmkit-init/references/update-supplement.md +9 -0
  166. package/bundled/skills-windows/recovery-workflow/SKILL.md +456 -0
  167. package/bundled/skills-windows/recovery-workflow/evals/evals.json +46 -0
  168. package/bundled/skills-windows/recovery-workflow/scripts/detect-recovery-state.py +544 -0
  169. package/bundled/skills-windows/refactor-pipeline-launcher/SKILL.md +406 -0
  170. package/bundled/skills-windows/refactor-planner/SKILL.md +540 -0
  171. package/bundled/skills-windows/refactor-planner/assets/planning-guide.md +292 -0
  172. package/bundled/skills-windows/refactor-planner/references/behavior-preservation.md +301 -0
  173. package/bundled/skills-windows/refactor-planner/references/refactor-scoping-guide.md +221 -0
  174. package/bundled/skills-windows/refactor-planner/scripts/validate-and-generate-refactor.py +858 -0
  175. package/bundled/skills-windows/refactor-workflow/SKILL.md +503 -0
  176. package/package.json +3 -2
  177. package/src/clean.js +73 -2
  178. package/src/config.js +159 -50
  179. package/src/detect-platform.js +16 -8
  180. package/src/external-skills.js +26 -19
  181. package/src/index.js +31 -9
  182. package/src/manifest.js +6 -2
  183. package/src/metadata.js +43 -5
  184. package/src/platforms.js +36 -0
  185. package/src/prompts.js +31 -6
  186. package/src/runtimes.js +20 -0
  187. package/src/scaffold.js +314 -110
  188. package/src/upgrade.js +81 -41
@@ -0,0 +1,140 @@
1
+ . "$PSScriptRoot\common.ps1"
2
+
3
+ function Get-PrizmDaemonProcess {
4
+ param([string]$PidFile)
5
+ if (-not (Test-Path $PidFile)) { return $null }
6
+ $rawPid = (Get-Content $PidFile -ErrorAction SilentlyContinue | Select-Object -First 1)
7
+ $processId = 0
8
+ if (-not [int]::TryParse($rawPid, [ref]$processId)) { return $null }
9
+ return Get-Process -Id $processId -ErrorAction SilentlyContinue
10
+ }
11
+
12
+ function Convert-PrizmEnvString {
13
+ param([string]$EnvString)
14
+ $envMap = @{}
15
+ foreach ($pair in ($EnvString -split '\s+')) {
16
+ if (-not $pair) { continue }
17
+ $parts = $pair -split '=', 2
18
+ if ($parts.Count -ne 2 -or -not $parts[0]) {
19
+ Write-PrizmWarn "Ignoring invalid env override: $pair"
20
+ continue
21
+ }
22
+ if ($parts[0] -notmatch '^[A-Za-z_][A-Za-z0-9_]*$') {
23
+ Write-PrizmWarn "Ignoring invalid env variable name: $($parts[0])"
24
+ continue
25
+ }
26
+ $envMap[$parts[0]] = $parts[1]
27
+ }
28
+ return $envMap
29
+ }
30
+
31
+ function Invoke-PrizmDaemon {
32
+ param(
33
+ [ValidateSet('feature','bugfix','refactor')][string]$Kind,
34
+ [string]$ScriptRoot,
35
+ [string]$RunScript,
36
+ [string[]]$Args
37
+ )
38
+
39
+ $global:PRIZM_DAEMON_EXIT_CODE = 0
40
+ $paths = Initialize-PrizmPaths $ScriptRoot
41
+ $command = if ($Args.Count -gt 0) { $Args[0] } else { 'start' }
42
+ $remaining = if ($Args.Count -gt 1) { $Args[1..($Args.Count - 1)] } else { @() }
43
+ $daemonDir = Join-Path $paths.PrizmkitDir 'state\daemon'
44
+ New-Item -ItemType Directory -Force -Path $daemonDir | Out-Null
45
+ $pidFile = Join-Path $daemonDir "$Kind.pid"
46
+ $logFile = Join-Path $daemonDir "$Kind-daemon.log"
47
+ $errorLogFile = Join-Path $daemonDir "$Kind-daemon.err.log"
48
+
49
+ switch ($command) {
50
+ { $_ -in @('help','--help','-h') } {
51
+ Write-Host "Usage: .\launch-$Kind-daemon.ps1 [start|status|stop|logs] [run options]"
52
+ Write-Host " start Launch .\run-$Kind.ps1 run in the background"
53
+ Write-Host " status Show daemon process state"
54
+ Write-Host " stop Stop the daemon process if it is still running"
55
+ Write-Host " logs Print daemon stdout/stderr logs"
56
+ return
57
+ }
58
+ 'start' {
59
+ $existing = Get-PrizmDaemonProcess $pidFile
60
+ if ($existing) {
61
+ Write-PrizmWarn "$Kind daemon already running (PID $($existing.Id))."
62
+ return
63
+ }
64
+
65
+ $runArgs = @()
66
+ $envOverrides = @{}
67
+ for ($i = 0; $i -lt $remaining.Count; $i++) {
68
+ $arg = $remaining[$i]
69
+ if ($arg -eq '--env') {
70
+ if ($i + 1 -ge $remaining.Count) { throw '--env requires a value, for example --env "VERBOSE=1 MAX_RETRIES=5"' }
71
+ $parsedEnv = Convert-PrizmEnvString $remaining[$i + 1]
72
+ foreach ($key in $parsedEnv.Keys) {
73
+ $envOverrides[$key] = $parsedEnv[$key]
74
+ }
75
+ $i++
76
+ continue
77
+ }
78
+ $runArgs += $arg
79
+ }
80
+
81
+ $hostPath = Resolve-PrizmPowerShellHost
82
+ $arguments = Join-PrizmProcessArguments (@('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $RunScript, 'run') + $runArgs)
83
+ $originalEnv = @{}
84
+ foreach ($key in $envOverrides.Keys) {
85
+ $originalEnv[$key] = [Environment]::GetEnvironmentVariable($key, 'Process')
86
+ [Environment]::SetEnvironmentVariable($key, $envOverrides[$key], 'Process')
87
+ }
88
+ try {
89
+ $process = Start-Process -FilePath $hostPath `
90
+ -ArgumentList $arguments `
91
+ -WorkingDirectory $paths.ProjectRoot `
92
+ -RedirectStandardOutput $logFile `
93
+ -RedirectStandardError $errorLogFile `
94
+ -PassThru
95
+ } finally {
96
+ foreach ($key in $envOverrides.Keys) {
97
+ [Environment]::SetEnvironmentVariable($key, $originalEnv[$key], 'Process')
98
+ }
99
+ }
100
+ Set-Content -Path $pidFile -Value ([string]$process.Id) -Encoding UTF8
101
+ Write-PrizmSuccess "$Kind daemon started (PID $($process.Id))."
102
+ Write-PrizmInfo "Log: $logFile"
103
+ if (Test-Path $errorLogFile) { Write-PrizmInfo "Error log: $errorLogFile" }
104
+ return
105
+ }
106
+ 'status' {
107
+ $process = Get-PrizmDaemonProcess $pidFile
108
+ if ($process) {
109
+ Write-PrizmSuccess "$Kind daemon running (PID $($process.Id))."
110
+ } else {
111
+ if (Test-Path $pidFile) { Remove-Item -Force $pidFile }
112
+ Write-PrizmInfo "$Kind daemon is not running."
113
+ }
114
+ Write-PrizmInfo "Log: $logFile"
115
+ return
116
+ }
117
+ 'stop' {
118
+ $process = Get-PrizmDaemonProcess $pidFile
119
+ if (-not $process) {
120
+ if (Test-Path $pidFile) { Remove-Item -Force $pidFile }
121
+ Write-PrizmInfo "$Kind daemon is not running."
122
+ return
123
+ }
124
+ Stop-PrizmProcessTreeById -ProcessId $process.Id
125
+ Remove-Item -Force $pidFile
126
+ Write-PrizmSuccess "$Kind daemon stopped (PID $($process.Id))."
127
+ return
128
+ }
129
+ 'logs' {
130
+ if (Test-Path $logFile) { Get-Content $logFile -Tail 120 }
131
+ if (Test-Path $errorLogFile) { Get-Content $errorLogFile -Tail 120 }
132
+ return
133
+ }
134
+ default {
135
+ Write-PrizmError "Unknown daemon command: $command"
136
+ $global:PRIZM_DAEMON_EXIT_CODE = 1
137
+ return
138
+ }
139
+ }
140
+ }
@@ -0,0 +1,446 @@
1
+ . "$PSScriptRoot\common.ps1"
2
+ $global:PRIZM_EXIT_CODE = 0
3
+
4
+ function Invoke-PrizmPipeline {
5
+ param(
6
+ [ValidateSet('feature','bugfix','refactor')][string]$Kind,
7
+ [string]$ScriptRoot,
8
+ [string[]]$Args
9
+ )
10
+
11
+ $paths = Initialize-PrizmPaths $ScriptRoot
12
+ Import-PrizmEnv (Join-Path $paths.PrizmkitDir '.env')
13
+ Set-Location $paths.ProjectRoot
14
+ $python = Resolve-PrizmPython
15
+
16
+ $command = if ($Args.Count -gt 0) { $Args[0] } else { 'help' }
17
+ $remaining = if ($Args.Count -gt 1) { $Args[1..($Args.Count - 1)] } else { @() }
18
+
19
+ $idName = @{ feature = 'feature'; bugfix = 'bug'; refactor = 'refactor' }[$Kind]
20
+ $idOption = @{ feature = '--feature-id'; bugfix = '--bug-id'; refactor = '--refactor-id' }[$Kind]
21
+ $listOption = @{ feature = '--feature-list'; bugfix = '--bug-list'; refactor = '--refactor-list' }[$Kind]
22
+ $initScript = @{ feature = 'init-pipeline.py'; bugfix = 'init-bugfix-pipeline.py'; refactor = 'init-refactor-pipeline.py' }[$Kind]
23
+ $updateScript = @{ feature = 'update-feature-status.py'; bugfix = 'update-bug-status.py'; refactor = 'update-refactor-status.py' }[$Kind]
24
+ $promptScript = @{ feature = 'generate-bootstrap-prompt.py'; bugfix = 'generate-bugfix-prompt.py'; refactor = 'generate-refactor-prompt.py' }[$Kind]
25
+ $defaultList = Get-PrizmListDefault $Kind $paths.ProjectRoot
26
+ $stateDir = Get-PrizmStateDir $Kind $paths.ProjectRoot
27
+ $envMaxRetryArgs = @()
28
+ if ($env:MAX_RETRIES) {
29
+ $parsedEnvMaxRetries = 0
30
+ if (-not [int]::TryParse($env:MAX_RETRIES, [ref]$parsedEnvMaxRetries) -or $parsedEnvMaxRetries -lt 1) {
31
+ throw "MAX_RETRIES must be a positive integer: $($env:MAX_RETRIES)"
32
+ }
33
+ $envMaxRetryArgs = @('--max-retries', [string]$parsedEnvMaxRetries)
34
+ }
35
+
36
+ if ($command -in @('help','--help','-h')) {
37
+ Write-Host "Usage: .\run-$Kind.ps1 run [item-id] [list-path] [--dry-run] [--mode lite|standard|full] [--critic] [--max-retries N] [--timeout seconds]"
38
+ Write-Host " .\run-$Kind.ps1 status [list-path]"
39
+ Write-Host " .\run-$Kind.ps1 reset"
40
+ $global:PRIZM_EXIT_CODE = 0
41
+ return
42
+ }
43
+
44
+ if ($command -eq 'reset') {
45
+ if (Test-Path $stateDir) { Remove-Item -Recurse -Force $stateDir }
46
+ New-Item -ItemType Directory -Force -Path $stateDir | Out-Null
47
+ Write-PrizmSuccess "State reset: $stateDir"
48
+ $global:PRIZM_EXIT_CODE = 0
49
+ return
50
+ }
51
+
52
+ if ($command -eq 'status') {
53
+ $listPath = if ($remaining.Count -gt 0) { $remaining[0] } else { $defaultList }
54
+ Invoke-PrizmPythonText $python (@((Join-Path $paths.ScriptsDir $updateScript), $listOption, $listPath, '--state-dir', $stateDir, '--action', 'status') + $envMaxRetryArgs)
55
+ $global:PRIZM_EXIT_CODE = $LASTEXITCODE
56
+ return
57
+ }
58
+
59
+ if ($command -ne 'run') { throw "Unknown command: $command" }
60
+
61
+ $itemId = $null
62
+ $listPath = $defaultList
63
+ $dryRun = $false
64
+ $mode = $null
65
+ if ($env:PIPELINE_MODE) { $mode = $env:PIPELINE_MODE.Trim() }
66
+ $critic = $null
67
+ $criticEnv = if ($env:ENABLE_CRITIC) { $env:ENABLE_CRITIC.Trim().ToLowerInvariant() } else { '' }
68
+ if ($criticEnv -in @('true','1','yes','on')) {
69
+ $critic = 'true'
70
+ } elseif ($criticEnv -in @('false','0','no','off')) {
71
+ $critic = 'false'
72
+ } elseif ($criticEnv) {
73
+ Write-PrizmWarn "Ignoring unsupported ENABLE_CRITIC value: $($env:ENABLE_CRITIC)"
74
+ }
75
+ $maxRetries = $null
76
+ if ($envMaxRetryArgs.Count -gt 0) { $maxRetries = [int]$envMaxRetryArgs[1] }
77
+ $verboseEnabled = $env:VERBOSE -in @('1','true','yes','on')
78
+ $timeoutSeconds = 0
79
+ if ($env:SESSION_TIMEOUT) {
80
+ $parsedEnvTimeout = 0
81
+ if (-not [int]::TryParse($env:SESSION_TIMEOUT, [ref]$parsedEnvTimeout) -or $parsedEnvTimeout -lt 0) {
82
+ throw "SESSION_TIMEOUT must be a non-negative integer: $($env:SESSION_TIMEOUT)"
83
+ }
84
+ $timeoutSeconds = $parsedEnvTimeout
85
+ }
86
+ $featuresFilter = $null
87
+
88
+ for ($i = 0; $i -lt $remaining.Count; $i++) {
89
+ $arg = $remaining[$i]
90
+ switch -Regex ($arg) {
91
+ '^--dry-run$' { $dryRun = $true; continue }
92
+ '^--critic$' { $critic = 'true'; continue }
93
+ '^--no-critic$' { $critic = 'false'; continue }
94
+ '^--mode$' { $i++; $mode = $remaining[$i]; continue }
95
+ '^--max-retries$' {
96
+ $i++
97
+ $parsedMaxRetries = 0
98
+ if (-not [int]::TryParse($remaining[$i], [ref]$parsedMaxRetries) -or $parsedMaxRetries -lt 1) {
99
+ throw "--max-retries must be a positive integer: $($remaining[$i])"
100
+ }
101
+ $maxRetries = $parsedMaxRetries
102
+ continue
103
+ }
104
+ '^--timeout$' {
105
+ $i++
106
+ if ($i -ge $remaining.Count) { throw '--timeout requires a seconds value.' }
107
+ $parsedTimeout = 0
108
+ if (-not [int]::TryParse($remaining[$i], [ref]$parsedTimeout) -or $parsedTimeout -lt 0) {
109
+ throw "--timeout must be a non-negative integer: $($remaining[$i])"
110
+ }
111
+ $timeoutSeconds = $parsedTimeout
112
+ continue
113
+ }
114
+ '^--features$' { $i++; $featuresFilter = $remaining[$i]; continue }
115
+ '^-' { Write-PrizmWarn "Ignoring unsupported option: $arg"; continue }
116
+ default {
117
+ if (-not $itemId -and $arg -match '^[FBR]-\d+') { $itemId = $arg; continue }
118
+ $listPath = $arg
119
+ }
120
+ }
121
+ }
122
+
123
+ if ($mode -and $mode -notin @('lite','standard','full')) {
124
+ throw "PIPELINE_MODE/--mode must be one of: lite, standard, full. Got: $mode"
125
+ }
126
+ $maxRetryArgs = @()
127
+ if ($maxRetries -ne $null) { $maxRetryArgs = @('--max-retries', [string]$maxRetries) }
128
+ if ($verboseEnabled) {
129
+ $modeLabel = if ($mode) { $mode } else { 'auto' }
130
+ $criticLabel = if ($critic) { $critic } else { 'plan-default' }
131
+ $retryLabel = if ($maxRetries -ne $null) { [string]$maxRetries } else { 'default' }
132
+ Write-PrizmInfo "Verbose mode enabled."
133
+ Write-PrizmInfo "Effective options: mode=$modeLabel critic=$criticLabel maxRetries=$retryLabel timeoutSeconds=$timeoutSeconds dryRun=$dryRun"
134
+ }
135
+
136
+ if (-not (Test-Path $listPath)) { throw "List file not found: $listPath" }
137
+ $pipelineStatePath = Join-Path $stateDir 'pipeline.json'
138
+ if (-not $dryRun) {
139
+ New-Item -ItemType Directory -Force -Path $stateDir | Out-Null
140
+ if (-not (Test-Path $pipelineStatePath)) {
141
+ Invoke-PrizmPythonText $python @((Join-Path $paths.ScriptsDir $initScript), $listOption, $listPath, '--state-dir', $stateDir)
142
+ } else {
143
+ Write-PrizmInfo "Using existing pipeline state: $pipelineStatePath"
144
+ }
145
+ } elseif (-not (Test-Path $pipelineStatePath)) {
146
+ Write-PrizmInfo "Dry-run mode: not initializing pipeline state."
147
+ }
148
+
149
+ function Test-PrizmGitRepository {
150
+ param([string]$ProjectRoot)
151
+ & git -C $ProjectRoot rev-parse --is-inside-work-tree *> $null
152
+ return $LASTEXITCODE -eq 0
153
+ }
154
+
155
+ function Get-PrizmGitHead {
156
+ param([string]$ProjectRoot)
157
+ $head = & git -C $ProjectRoot rev-parse HEAD 2>$null
158
+ if ($LASTEXITCODE -eq 0 -and $head) { return ($head | Select-Object -First 1) }
159
+ return ''
160
+ }
161
+
162
+ function Test-PrizmGitCommitsSince {
163
+ param([string]$ProjectRoot, [string]$BaseCommit)
164
+ if (-not $BaseCommit) {
165
+ return -not [string]::IsNullOrWhiteSpace([string](Get-PrizmGitHead $ProjectRoot))
166
+ }
167
+ $commit = & git -C $ProjectRoot log "$BaseCommit..HEAD" --oneline 2>$null | Select-Object -First 1
168
+ return -not [string]::IsNullOrWhiteSpace([string]$commit)
169
+ }
170
+
171
+ function ConvertTo-PrizmGitRelativePath {
172
+ param([string]$ProjectRoot, [string]$Path)
173
+ if (-not $Path) { return '' }
174
+ try {
175
+ $rootFull = [System.IO.Path]::GetFullPath($ProjectRoot).TrimEnd([char[]]@('\', '/')) + [System.IO.Path]::DirectorySeparatorChar
176
+ $pathInput = if ([System.IO.Path]::IsPathRooted($Path)) { $Path } else { Join-Path $ProjectRoot $Path }
177
+ $pathFull = [System.IO.Path]::GetFullPath($pathInput)
178
+ $rootUri = [System.Uri]::new($rootFull)
179
+ $pathUri = [System.Uri]::new($pathFull)
180
+ return ([System.Uri]::UnescapeDataString($rootUri.MakeRelativeUri($pathUri).ToString()) -replace '\\', '/').TrimStart([char[]]@('.', '/'))
181
+ } catch {
182
+ return ($Path -replace '\\', '/').TrimStart([char[]]@('.', '/'))
183
+ }
184
+ }
185
+
186
+ function Get-PrizmGitDirtyPaths {
187
+ param([string]$ProjectRoot)
188
+ $lines = & git -C $ProjectRoot status --porcelain 2>$null
189
+ $paths = @()
190
+ foreach ($line in $lines) {
191
+ if ([string]::IsNullOrWhiteSpace($line) -or $line.Length -lt 4) { continue }
192
+ $path = $line.Substring(3).Trim()
193
+ if ($path -match ' -> ') { $path = ($path -split ' -> ')[-1].Trim() }
194
+ $paths += (($path -replace '\\', '/').Trim('"').TrimStart([char[]]@('.', '/')))
195
+ }
196
+ return @($paths)
197
+ }
198
+
199
+ function Test-PrizmPipelineBookkeepingPath {
200
+ param(
201
+ [string]$ProjectRoot,
202
+ [string]$Path,
203
+ [string]$StateDir,
204
+ [string]$ListPath
205
+ )
206
+ $normalizedPath = (($Path -replace '\\', '/').Trim('"').TrimStart([char[]]@('.', '/')))
207
+ $stateRel = ConvertTo-PrizmGitRelativePath $ProjectRoot $StateDir
208
+ $listRel = ConvertTo-PrizmGitRelativePath $ProjectRoot $ListPath
209
+ if ($stateRel -and ($normalizedPath -eq $stateRel -or $normalizedPath.StartsWith("$stateRel/"))) { return $true }
210
+ if ($listRel -and $normalizedPath -eq $listRel) { return $true }
211
+ return $false
212
+ }
213
+
214
+ function Test-PrizmGitWorkDirty {
215
+ param([string]$ProjectRoot, [string]$StateDir, [string]$ListPath)
216
+ foreach ($path in (Get-PrizmGitDirtyPaths $ProjectRoot)) {
217
+ if (-not (Test-PrizmPipelineBookkeepingPath $ProjectRoot $path $StateDir $ListPath)) {
218
+ return $true
219
+ }
220
+ }
221
+ return $false
222
+ }
223
+
224
+ function Test-PrizmGitDirty {
225
+ param([string]$ProjectRoot)
226
+ $dirty = & git -C $ProjectRoot status --porcelain 2>$null | Select-Object -First 1
227
+ return -not [string]::IsNullOrWhiteSpace([string]$dirty)
228
+ }
229
+
230
+ function Invoke-PrizmGitAutoCommit {
231
+ param([string]$ProjectRoot, [string]$Message)
232
+ & git -C $ProjectRoot add -A *> $null
233
+ & git -C $ProjectRoot commit --no-verify -m $Message *> $null
234
+ return $LASTEXITCODE -eq 0
235
+ }
236
+
237
+ function Invoke-PrizmGitIncludeRemainingArtifacts {
238
+ param([string]$ProjectRoot, [string]$ItemId)
239
+ & git -C $ProjectRoot add -A *> $null
240
+ & git -C $ProjectRoot commit --no-verify --amend --no-edit *> $null
241
+ if ($LASTEXITCODE -eq 0) { return }
242
+ & git -C $ProjectRoot commit --no-verify -m "chore($ItemId): include remaining session artifacts" *> $null
243
+ }
244
+
245
+ function Invoke-PrizmGitIncludeBookkeepingArtifacts {
246
+ param([string]$ProjectRoot, [string]$StateDir, [string]$ListPath)
247
+ $listRel = ConvertTo-PrizmGitRelativePath $ProjectRoot $ListPath
248
+ if (-not $listRel) { return }
249
+ & git -C $ProjectRoot ls-files --error-unmatch -- $listRel *> $null
250
+ if ($LASTEXITCODE -ne 0) { return }
251
+ & git -C $ProjectRoot commit --no-verify --amend --no-edit --only -- $listRel *> $null
252
+ }
253
+
254
+ function Invoke-PrizmPipelineItem {
255
+ param([string]$CurrentItemId)
256
+ $script:PRIZM_ITEM_EXIT_CODE = 0
257
+
258
+ $sessionId = New-PrizmSessionId $Kind
259
+ $runId = "run-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
260
+ $retryCount = '0'
261
+ $resumePhase = 'null'
262
+ $isGitRepository = if (-not $dryRun) { Test-PrizmGitRepository $paths.ProjectRoot } else { $false }
263
+ $baseCommit = if ($isGitRepository) { Get-PrizmGitHead $paths.ProjectRoot } else { '' }
264
+ $hadDirtyBaseline = if ($isGitRepository) { Test-PrizmGitWorkDirty $paths.ProjectRoot $stateDir $listPath } else { $false }
265
+ if ($hadDirtyBaseline) {
266
+ Write-PrizmWarn "Dirty working tree detected before pipeline bookkeeping; session success requires a new commit."
267
+ }
268
+
269
+ if ($dryRun) {
270
+ $statusPath = Join-Path $stateDir "$($idName)s\$CurrentItemId\status.json"
271
+ if (Test-Path $statusPath) {
272
+ try {
273
+ $itemStatus = Get-Content -Raw $statusPath | ConvertFrom-Json
274
+ if ($itemStatus.retry_count -ne $null) { $retryCount = [string]$itemStatus.retry_count }
275
+ if ($itemStatus.resume_from_phase -ne $null) { $resumePhase = [string]$itemStatus.resume_from_phase }
276
+ } catch {
277
+ Write-PrizmWarn "Could not read dry-run status file: $statusPath"
278
+ }
279
+ }
280
+ } else {
281
+ $start = Invoke-PrizmPythonJson $python (@((Join-Path $paths.ScriptsDir $updateScript), $listOption, $listPath, '--state-dir', $stateDir, '--action', 'start', $idOption, $CurrentItemId, '--session-id', $sessionId) + $maxRetryArgs)
282
+ if ($start.retry_count -ne $null) { $retryCount = [string]$start.retry_count }
283
+ if ($start.resume_from_phase -ne $null) { $resumePhase = [string]$start.resume_from_phase }
284
+ }
285
+
286
+ $sessionDir = if ($dryRun) {
287
+ Join-Path ([System.IO.Path]::GetTempPath()) "prizmkit-dry-run\$sessionId"
288
+ } else {
289
+ Join-Path $stateDir "$($idName)s\$CurrentItemId\sessions\$sessionId"
290
+ }
291
+ $logsDir = Join-Path $sessionDir 'logs'
292
+ New-Item -ItemType Directory -Force -Path $logsDir | Out-Null
293
+ $promptPath = Join-Path $sessionDir 'bootstrap-prompt.md'
294
+ $sessionLog = Join-Path $logsDir 'session.log'
295
+ $pidPath = Join-Path $logsDir 'ai.pid'
296
+
297
+ $cli = $null
298
+ if (-not $dryRun) {
299
+ $cli = Resolve-PrizmAiCli $paths.ProjectRoot $paths.PrizmkitDir
300
+ $env:PRIZMKIT_PLATFORM = Get-PrizmPlatformFromProject $paths.ProjectRoot $paths.PrizmkitDir $cli
301
+ }
302
+
303
+ $promptArgs = @((Join-Path $paths.ScriptsDir $promptScript), $listOption, $listPath, $idOption, $CurrentItemId, '--session-id', $sessionId, '--run-id', $runId, '--retry-count', $retryCount, '--resume-phase', $resumePhase, '--state-dir', $stateDir, '--output', $promptPath)
304
+ if ($mode) { $promptArgs += @('--mode', $mode) }
305
+ if ($critic) { $promptArgs += @('--critic', $critic) }
306
+ if ($dryRun) { $promptArgs += '--no-checkpoint' }
307
+ if ($verboseEnabled) { Write-PrizmInfo "Prompt args: $($promptArgs -join ' ')" }
308
+ $promptResult = Invoke-PrizmPythonJson $python $promptArgs
309
+ $itemModel = ''
310
+ if ($promptResult -and $promptResult.PSObject.Properties['model'] -and $promptResult.model) {
311
+ $itemModel = [string]$promptResult.model
312
+ }
313
+ $effectiveModel = if ($itemModel) { $itemModel } else { $env:MODEL }
314
+ Write-PrizmSuccess "Generated prompt: $promptPath"
315
+
316
+ if ($dryRun) {
317
+ Get-Content $promptPath
318
+ $script:PRIZM_ITEM_EXIT_CODE = 0
319
+ return
320
+ }
321
+
322
+ Write-PrizmInfo "Starting $cli session for $CurrentItemId ($sessionId)"
323
+
324
+ $job = Start-Job -ScriptBlock {
325
+ param($commonPath, $cli, $promptPath, $sessionLog, $projectRoot, $model, $pidPath)
326
+ . $commonPath
327
+ Invoke-PrizmAiSession -CliCommand $cli -PromptPath $promptPath -LogPath $sessionLog -ProjectRoot $projectRoot -Model $model -PidPath $pidPath
328
+ } -ArgumentList (Join-Path $paths.PipelineDir 'lib\common.ps1'), $cli, $promptPath, $sessionLog, $paths.ProjectRoot, $effectiveModel, $pidPath
329
+
330
+ $completed = if ($timeoutSeconds -le 0) {
331
+ Wait-Job $job
332
+ } else {
333
+ Wait-Job $job -Timeout $timeoutSeconds
334
+ }
335
+ if (-not $completed) {
336
+ if (Test-Path $pidPath) {
337
+ $rawPid = Get-Content $pidPath -ErrorAction SilentlyContinue | Select-Object -First 1
338
+ $aiPid = 0
339
+ if ([int]::TryParse($rawPid, [ref]$aiPid)) {
340
+ Stop-PrizmProcessTreeById -ProcessId $aiPid
341
+ }
342
+ }
343
+ Stop-Job $job
344
+ Remove-Job $job
345
+ Invoke-PrizmPythonText $python (@((Join-Path $paths.ScriptsDir $updateScript), $listOption, $listPath, '--state-dir', $stateDir, '--action', 'update', $idOption, $CurrentItemId, '--session-id', $sessionId, '--session-status', 'timed_out') + $maxRetryArgs)
346
+ throw "AI session timed out after $timeoutSeconds seconds. Log: $sessionLog"
347
+ }
348
+
349
+ $exitCode = [int](Receive-Job $job)
350
+ Remove-Job $job
351
+ $status = 'crashed'
352
+ if ($exitCode -ne 0) {
353
+ Write-PrizmWarn "AI session exited with code $exitCode"
354
+ } elseif (-not $isGitRepository) {
355
+ Write-PrizmWarn "AI session exited cleanly, but project is not a git repository; cannot verify work was committed."
356
+ } elseif (Test-PrizmGitCommitsSince $paths.ProjectRoot $baseCommit) {
357
+ $status = 'success'
358
+ } elseif ($hadDirtyBaseline) {
359
+ Write-PrizmWarn "AI session exited cleanly but produced no new commits; pre-existing dirty tree will not be auto-committed."
360
+ } elseif (Test-PrizmGitWorkDirty $paths.ProjectRoot $stateDir $listPath) {
361
+ Write-PrizmWarn "AI session exited cleanly but produced no commits; auto-committing dirty work tree."
362
+ if (Invoke-PrizmGitAutoCommit $paths.ProjectRoot "chore($CurrentItemId): auto-commit session work") {
363
+ $status = 'success'
364
+ } else {
365
+ Write-PrizmWarn "Auto-commit failed; treating session as crashed."
366
+ }
367
+ } else {
368
+ Write-PrizmWarn "AI session exited cleanly but produced no commits and no changes."
369
+ }
370
+
371
+ Invoke-PrizmPythonText $python (@((Join-Path $paths.ScriptsDir $updateScript), $listOption, $listPath, '--state-dir', $stateDir, '--action', 'update', $idOption, $CurrentItemId, '--session-id', $sessionId, '--session-status', $status) + $maxRetryArgs)
372
+
373
+ if ($status -eq 'success' -and (Test-PrizmGitDirty $paths.ProjectRoot)) {
374
+ if ($hadDirtyBaseline) {
375
+ Write-PrizmInfo "Auto-committing pipeline bookkeeping artifacts only."
376
+ Invoke-PrizmGitIncludeBookkeepingArtifacts $paths.ProjectRoot $stateDir $listPath
377
+ } else {
378
+ Write-PrizmInfo "Auto-committing remaining session artifacts."
379
+ Invoke-PrizmGitIncludeRemainingArtifacts $paths.ProjectRoot $CurrentItemId
380
+ }
381
+ }
382
+
383
+ if ($status -eq 'success') {
384
+ Write-PrizmSuccess "$Kind item completed: $CurrentItemId"
385
+ } else {
386
+ Write-PrizmError "$Kind item failed: $CurrentItemId. Log: $sessionLog"
387
+ }
388
+ $script:PRIZM_ITEM_EXIT_CODE = if ($status -eq 'success') { 0 } else { 1 }
389
+ return
390
+ }
391
+
392
+ if ($itemId) {
393
+ Invoke-PrizmPipelineItem $itemId
394
+ $global:PRIZM_EXIT_CODE = $script:PRIZM_ITEM_EXIT_CODE
395
+ return
396
+ }
397
+
398
+ $processedCount = 0
399
+ $lastExitCode = 0
400
+ while ($true) {
401
+ $getNextArgs = @((Join-Path $paths.ScriptsDir $updateScript), $listOption, $listPath, '--state-dir', $stateDir, '--action', 'get_next')
402
+ if ($featuresFilter -and $Kind -eq 'feature') { $getNextArgs += @('--features', $featuresFilter) }
403
+ $getNextOutput = (Invoke-PrizmPythonOutput $python $getNextArgs).Trim()
404
+ if (-not $getNextOutput -or $getNextOutput -eq 'PIPELINE_COMPLETE') {
405
+ if ($processedCount -eq 0) {
406
+ Write-PrizmSuccess "No pending $Kind items."
407
+ } else {
408
+ Write-PrizmSuccess "Processed $processedCount $Kind item(s)."
409
+ }
410
+ $global:PRIZM_EXIT_CODE = $lastExitCode
411
+ return
412
+ }
413
+ if ($getNextOutput -eq 'PIPELINE_BLOCKED') {
414
+ Write-PrizmWarn "All remaining $Kind items are blocked."
415
+ Write-PrizmWarn "Run .\run-$Kind.ps1 status to see details."
416
+ $global:PRIZM_EXIT_CODE = $lastExitCode
417
+ return
418
+ }
419
+ if ($getNextOutput -notmatch '^\s*[\{\[]') {
420
+ throw "Unexpected get_next output: $getNextOutput"
421
+ }
422
+ $next = $getNextOutput | ConvertFrom-Json
423
+ $nextItemId = $next.PSObject.Properties["${idName}_id"].Value
424
+ if (-not $nextItemId) {
425
+ if ($processedCount -eq 0) {
426
+ Write-PrizmSuccess "No pending $Kind items."
427
+ } else {
428
+ Write-PrizmSuccess "Processed $processedCount $Kind item(s)."
429
+ }
430
+ $global:PRIZM_EXIT_CODE = $lastExitCode
431
+ return
432
+ }
433
+
434
+ $processedCount++
435
+ Invoke-PrizmPipelineItem $nextItemId
436
+ $lastExitCode = $script:PRIZM_ITEM_EXIT_CODE
437
+ if ($dryRun) {
438
+ $global:PRIZM_EXIT_CODE = $lastExitCode
439
+ return
440
+ }
441
+ if ($lastExitCode -ne 0 -and $env:STOP_ON_FAILURE -eq '1') {
442
+ $global:PRIZM_EXIT_CODE = $lastExitCode
443
+ return
444
+ }
445
+ }
446
+ }
@@ -0,0 +1,87 @@
1
+ . "$PSScriptRoot\common.ps1"
2
+
3
+ function Get-PrizmFeatureSlug {
4
+ param([string]$ListPath, [string]$FeatureId)
5
+ $data = Get-Content $ListPath -Raw | ConvertFrom-Json
6
+ $feature = $data.features | Where-Object { $_.id -eq $FeatureId } | Select-Object -First 1
7
+ if (-not $feature) { throw "Feature not found in list: $FeatureId" }
8
+ $numeric = ($FeatureId -replace '^[Ff]-', '').PadLeft(3, '0')
9
+ $slug = ([string]$feature.title).ToLowerInvariant()
10
+ $slug = $slug -replace '[^a-z0-9\s-]', ''
11
+ $slug = ($slug.Trim() -replace '\s+', '-')
12
+ $slug = ($slug -replace '-+', '-').Trim('-')
13
+ if (-not $slug) { $slug = 'feature' }
14
+ return "$numeric-$slug"
15
+ }
16
+
17
+ function Invoke-PrizmReset {
18
+ param(
19
+ [ValidateSet('feature','bugfix','refactor')][string]$Kind,
20
+ [string]$ScriptRoot,
21
+ [string[]]$Args
22
+ )
23
+
24
+ $global:PRIZM_RESET_EXIT_CODE = 0
25
+ $paths = Initialize-PrizmPaths $ScriptRoot
26
+ Set-Location $paths.ProjectRoot
27
+ $python = Resolve-PrizmPython
28
+
29
+ $idOption = @{ feature = '--feature-id'; bugfix = '--bug-id'; refactor = '--refactor-id' }[$Kind]
30
+ $listOption = @{ feature = '--feature-list'; bugfix = '--bug-list'; refactor = '--refactor-list' }[$Kind]
31
+ $updateScript = @{ feature = 'update-feature-status.py'; bugfix = 'update-bug-status.py'; refactor = 'update-refactor-status.py' }[$Kind]
32
+ $runScript = @{ feature = 'run-feature.ps1'; bugfix = 'run-bugfix.ps1'; refactor = 'run-refactor.ps1' }[$Kind]
33
+ $idPattern = @{ feature = '^[Ff]-\d+'; bugfix = '^[Bb]-\d+'; refactor = '^[Rr]-\d+' }[$Kind]
34
+ $label = @{ feature = 'feature'; bugfix = 'bug'; refactor = 'refactor' }[$Kind]
35
+ $listPath = Get-PrizmListDefault $Kind $paths.ProjectRoot
36
+ $stateDir = Get-PrizmStateDir $Kind $paths.ProjectRoot
37
+ $itemId = $null
38
+ $clean = $false
39
+ $runAfterReset = $false
40
+
41
+ foreach ($arg in $Args) {
42
+ switch -Regex ($arg) {
43
+ '^--clean$' { $clean = $true; continue }
44
+ '^--run$' { $runAfterReset = $true; continue }
45
+ '^(--help|-h)$' {
46
+ Write-Host "Usage: .\reset-$label.ps1 [$($label)-id] [--clean] [--run] [list-path]"
47
+ Write-Host " No ID clears the whole $Kind pipeline state."
48
+ Write-Host " With an ID, reset only that item. --clean removes item artifacts. --run retries it immediately."
49
+ return
50
+ }
51
+ '^-.*' { Write-PrizmWarn "Ignoring unsupported option: $arg"; continue }
52
+ default {
53
+ if (-not $itemId -and $arg -match $idPattern) {
54
+ $itemId = $arg.ToUpperInvariant()
55
+ } else {
56
+ $listPath = $arg
57
+ }
58
+ }
59
+ }
60
+ }
61
+
62
+ if (-not $itemId) {
63
+ & (Join-Path $paths.PipelineDir $runScript) reset
64
+ $global:PRIZM_RESET_EXIT_CODE = $LASTEXITCODE
65
+ return
66
+ }
67
+
68
+ if (-not (Test-Path $listPath)) { throw "List file not found: $listPath" }
69
+ New-Item -ItemType Directory -Force -Path $stateDir | Out-Null
70
+
71
+ $action = if ($clean) { 'clean' } else { 'reset' }
72
+ $resetArgs = @((Join-Path $paths.ScriptsDir $updateScript), $listOption, $listPath, '--state-dir', $stateDir, '--action', $action, $idOption, $itemId)
73
+ if ($clean) {
74
+ $resetArgs += @('--project-root', $paths.ProjectRoot)
75
+ if ($Kind -eq 'feature') {
76
+ $resetArgs += @('--feature-slug', (Get-PrizmFeatureSlug $listPath $itemId))
77
+ }
78
+ }
79
+
80
+ Invoke-PrizmPythonText $python $resetArgs
81
+ Write-PrizmSuccess "Reset $label item: $itemId"
82
+
83
+ if ($runAfterReset) {
84
+ & (Join-Path $paths.PipelineDir $runScript) run $itemId $listPath
85
+ $global:PRIZM_RESET_EXIT_CODE = $LASTEXITCODE
86
+ }
87
+ }
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env pwsh
2
+ . "$PSScriptRoot\lib\reset.ps1"
3
+ try {
4
+ Invoke-PrizmReset -Kind bugfix -ScriptRoot $PSScriptRoot -Args $args
5
+ exit $global:PRIZM_RESET_EXIT_CODE
6
+ } catch {
7
+ Write-PrizmError $_.Exception.Message
8
+ exit 1
9
+ }