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,9 @@
1
+ #!/usr/bin/env pwsh
2
+ . "$PSScriptRoot\lib\reset.ps1"
3
+ try {
4
+ Invoke-PrizmReset -Kind feature -ScriptRoot $PSScriptRoot -Args $args
5
+ exit $global:PRIZM_RESET_EXIT_CODE
6
+ } catch {
7
+ Write-PrizmError $_.Exception.Message
8
+ exit 1
9
+ }
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env pwsh
2
+ . "$PSScriptRoot\lib\reset.ps1"
3
+ try {
4
+ Invoke-PrizmReset -Kind refactor -ScriptRoot $PSScriptRoot -Args $args
5
+ exit $global:PRIZM_RESET_EXIT_CODE
6
+ } catch {
7
+ Write-PrizmError $_.Exception.Message
8
+ exit 1
9
+ }
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env pwsh
2
+ . "$PSScriptRoot\lib\pipeline.ps1"
3
+ try {
4
+ Invoke-PrizmPipeline -Kind bugfix -ScriptRoot $PSScriptRoot -Args $args
5
+ exit $global:PRIZM_EXIT_CODE
6
+ } catch {
7
+ Write-PrizmError $_.Exception.Message
8
+ exit 1
9
+ }
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env pwsh
2
+ . "$PSScriptRoot\lib\pipeline.ps1"
3
+ try {
4
+ Invoke-PrizmPipeline -Kind feature -ScriptRoot $PSScriptRoot -Args $args
5
+ exit $global:PRIZM_EXIT_CODE
6
+ } catch {
7
+ Write-PrizmError $_.Exception.Message
8
+ exit 1
9
+ }
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env pwsh
2
+ . "$PSScriptRoot\lib\common.ps1"
3
+ $paths = Initialize-PrizmPaths $PSScriptRoot
4
+ Import-PrizmEnv (Join-Path $paths.PrizmkitDir '.env')
5
+ Set-Location $paths.ProjectRoot
6
+ $python = Resolve-PrizmPython
7
+
8
+ $command = if ($args.Count -gt 0 -and $args[0] -in @('run','detect','help','--help','-h')) { $args[0] } else { 'run' }
9
+ $remaining = if ($args.Count -gt 0 -and $args[0] -in @('run','detect','help','--help','-h')) {
10
+ if ($args.Count -gt 1) { $args[1..($args.Count - 1)] } else { @() }
11
+ } else {
12
+ $args
13
+ }
14
+
15
+ if ($command -in @('help','--help','-h')) {
16
+ Write-Host 'Usage: .\run-recovery.ps1 [run|detect] [--dry-run] [--yes] [--model <model>]'
17
+ Write-Host ' run Auto-detect interrupted workflow and start a recovery AI session'
18
+ Write-Host ' detect Print detection report only'
19
+ Write-Host ' --dry-run Generate and print the recovery prompt without starting AI'
20
+ Write-Host ' --yes Accepted for scripted use; no confirmation prompt is shown'
21
+ Write-Host ' --model Override MODEL for this recovery session'
22
+ exit 0
23
+ }
24
+
25
+ $dryRun = $false
26
+ $model = $env:MODEL
27
+ for ($i = 0; $i -lt $remaining.Count; $i++) {
28
+ $arg = $remaining[$i]
29
+ switch -Regex ($arg) {
30
+ '^--dry-run$' { $dryRun = $true; continue }
31
+ '^--yes$' { continue }
32
+ '^--model$' {
33
+ if ($i + 1 -ge $remaining.Count) { Write-PrizmError '--model requires a value'; exit 1 }
34
+ $i++
35
+ $model = $remaining[$i]
36
+ continue
37
+ }
38
+ default {
39
+ Write-PrizmWarn "Ignoring unsupported recovery option: $arg"
40
+ }
41
+ }
42
+ }
43
+
44
+ $recoveryDetectScript = $null
45
+ foreach ($candidate in @(
46
+ (Join-Path $paths.ProjectRoot '.agents\skills\recovery-workflow\scripts\detect-recovery-state.py'),
47
+ (Join-Path $paths.ProjectRoot '.claude\command-assets\recovery-workflow\scripts\detect-recovery-state.py'),
48
+ (Join-Path $paths.ProjectRoot '.codebuddy\skills\recovery-workflow\scripts\detect-recovery-state.py')
49
+ )) {
50
+ if (Test-Path $candidate) {
51
+ $recoveryDetectScript = $candidate
52
+ break
53
+ }
54
+ }
55
+ if (-not $recoveryDetectScript) {
56
+ Write-PrizmError 'Recovery detection script not found. Reinstall PrizmKit skills first.'
57
+ exit 1
58
+ }
59
+
60
+ $stateDir = Join-Path $paths.PrizmkitDir 'state\recovery'
61
+ New-Item -ItemType Directory -Force -Path $stateDir | Out-Null
62
+ $detectionPath = Join-Path $stateDir 'detection.json'
63
+ $promptPath = Join-Path $paths.PrizmkitDir 'state\recovery-bootstrap-prompt.md'
64
+ $logPath = Join-Path $paths.PrizmkitDir 'state\recovery-session.log'
65
+
66
+ Invoke-PrizmPythonText $python @($recoveryDetectScript, '--project-root', $paths.ProjectRoot) | Set-Content -Path $detectionPath -Encoding UTF8
67
+ if ($command -eq 'detect') { Get-Content $detectionPath; exit 0 }
68
+
69
+ Invoke-PrizmPythonText $python @((Join-Path $paths.ScriptsDir 'generate-recovery-prompt.py'), '--detection-json', $detectionPath, '--project-root', $paths.ProjectRoot, '--session-id', (New-PrizmSessionId 'recovery'), '--output', $promptPath)
70
+ if ($dryRun) { Get-Content $promptPath; exit 0 }
71
+ $cli = Resolve-PrizmAiCli $paths.ProjectRoot $paths.PrizmkitDir
72
+ $env:PRIZMKIT_PLATFORM = Get-PrizmPlatformFromProject $paths.ProjectRoot $paths.PrizmkitDir $cli
73
+ $pidPath = Join-Path $paths.PrizmkitDir 'state\recovery-session.pid'
74
+ $exitCode = Invoke-PrizmAiSession -CliCommand $cli -PromptPath $promptPath -LogPath $logPath -ProjectRoot $paths.ProjectRoot -Model $model -PidPath $pidPath
75
+ if ($exitCode -eq 0) { Write-PrizmSuccess "Recovery session completed." } else { Write-PrizmError "Recovery session failed. Log: $logPath" }
76
+ exit $exitCode
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env pwsh
2
+ . "$PSScriptRoot\lib\pipeline.ps1"
3
+ try {
4
+ Invoke-PrizmPipeline -Kind refactor -ScriptRoot $PSScriptRoot -Args $args
5
+ exit $global:PRIZM_EXIT_CODE
6
+ } catch {
7
+ Write-PrizmError $_.Exception.Message
8
+ exit 1
9
+ }
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env python3
2
+ """Parse a session-status.json file and output a simple status string for the shell runner.
3
+
4
+ Reads the session status written by an agent at session end, validates required
5
+ fields, and prints a single-line result to stdout. Detailed JSON is written to
6
+ stderr for logging.
7
+
8
+ Usage:
9
+ python3 check-session-status.py --status-file <path>
10
+ """
11
+
12
+ import argparse
13
+ import json
14
+ import sys
15
+
16
+ from utils import setup_logging
17
+
18
+
19
+ REQUIRED_FIELDS = ["session_id", "status", "timestamp"]
20
+ # At least one of these ID fields must be present (depends on pipeline type)
21
+ ID_FIELDS = ["feature_id", "bug_id", "refactor_id"]
22
+
23
+ LOGGER = setup_logging("check-session-status")
24
+
25
+
26
+ def parse_args():
27
+ parser = argparse.ArgumentParser(
28
+ description="Parse session-status.json and output a status string for the shell runner."
29
+ )
30
+ parser.add_argument(
31
+ "--status-file",
32
+ required=True,
33
+ help="Path to the session-status.json file",
34
+ )
35
+ return parser.parse_args()
36
+
37
+
38
+ def load_status_file(path):
39
+ """Load and parse the session status JSON file.
40
+
41
+ Returns (data, error_message). On success error_message is None.
42
+ """
43
+ try:
44
+ with open(path, "r", encoding="utf-8") as f:
45
+ data = json.load(f)
46
+ except (IOError, OSError) as e:
47
+ return None, "Cannot read status file: {}".format(str(e))
48
+ except (json.JSONDecodeError, ValueError) as e:
49
+ return None, "Malformed JSON in status file: {}".format(str(e))
50
+ return data, None
51
+
52
+
53
+ def validate_required_fields(data):
54
+ """Check that all required fields are present and non-empty.
55
+
56
+ Returns a list of missing/invalid field names.
57
+ In addition to the always-required fields, at least one of
58
+ feature_id / bug_id / refactor_id must be present.
59
+ """
60
+ missing = []
61
+ for field in REQUIRED_FIELDS:
62
+ if field not in data:
63
+ missing.append(field)
64
+ elif not isinstance(data[field], str) or not data[field].strip():
65
+ missing.append(field)
66
+
67
+ # Check that at least one ID field is present and non-empty
68
+ has_id = any(
69
+ field in data and isinstance(data[field], str) and data[field].strip()
70
+ for field in ID_FIELDS
71
+ )
72
+ if not has_id:
73
+ missing.append("feature_id|bug_id|refactor_id")
74
+
75
+ return missing
76
+
77
+
78
+ def determine_status(data):
79
+ """Determine the single-line status string from the parsed data.
80
+
81
+ Returns one of: success, partial_resumable, partial_not_resumable,
82
+ failed, commit_missing, docs_missing, merge_conflict.
83
+ """
84
+ status = data.get("status", "")
85
+
86
+ if status == "success":
87
+ return "success"
88
+ elif status == "partial":
89
+ can_resume = data.get("can_resume", False)
90
+ if can_resume:
91
+ return "partial_resumable"
92
+ else:
93
+ return "partial_not_resumable"
94
+ elif status == "partial_resumable":
95
+ return "partial_resumable"
96
+ elif status == "partial_not_resumable":
97
+ return "partial_not_resumable"
98
+ elif status == "failed":
99
+ return "failed"
100
+ elif status == "crashed":
101
+ return "crashed"
102
+ elif status == "timed_out":
103
+ return "timed_out"
104
+ elif status == "commit_missing":
105
+ return "commit_missing"
106
+ elif status == "docs_missing":
107
+ return "docs_missing"
108
+ elif status == "merge_conflict":
109
+ return "merge_conflict"
110
+ else:
111
+ # Unknown status value — treat as crashed
112
+ return "crashed"
113
+
114
+
115
+ def build_detail_report(data, resolved_status):
116
+ """Build the detailed JSON report for stderr logging."""
117
+ errors = data.get("errors", [])
118
+ error_count = len(errors) if isinstance(errors, list) else 0
119
+
120
+ return {
121
+ "status": resolved_status,
122
+ "feature_id": data.get("feature_id"),
123
+ "bug_id": data.get("bug_id"),
124
+ "refactor_id": data.get("refactor_id"),
125
+ "completed_phases": data.get("completed_phases", []),
126
+ "checkpoint_reached": data.get("checkpoint_reached"),
127
+ "tasks_completed": data.get("tasks_completed", 0),
128
+ "tasks_total": data.get("tasks_total", 0),
129
+ "error_count": error_count,
130
+ "can_resume": data.get("can_resume", False),
131
+ "resume_from_phase": data.get("resume_from_phase"),
132
+ }
133
+
134
+
135
+ def main():
136
+ args = parse_args()
137
+
138
+ # Load the status file
139
+ data, load_error = load_status_file(args.status_file)
140
+ if load_error is not None:
141
+ # File missing or malformed JSON
142
+ detail = {
143
+ "status": "crashed",
144
+ "feature_id": None,
145
+ "completed_phases": [],
146
+ "checkpoint_reached": None,
147
+ "tasks_completed": 0,
148
+ "tasks_total": 0,
149
+ "error_count": 1,
150
+ "can_resume": False,
151
+ "resume_from_phase": None,
152
+ "load_error": load_error,
153
+ }
154
+ sys.stderr.write(json.dumps(detail, indent=2, ensure_ascii=False) + "\n")
155
+ print("crashed")
156
+ sys.exit(0)
157
+
158
+ # Validate required fields
159
+ missing = validate_required_fields(data)
160
+ if missing:
161
+ detail = {
162
+ "status": "crashed",
163
+ "feature_id": data.get("feature_id"),
164
+ "completed_phases": data.get("completed_phases", []),
165
+ "checkpoint_reached": data.get("checkpoint_reached"),
166
+ "tasks_completed": data.get("tasks_completed", 0),
167
+ "tasks_total": data.get("tasks_total", 0),
168
+ "error_count": 1,
169
+ "can_resume": False,
170
+ "resume_from_phase": None,
171
+ "validation_error": "Missing or invalid required fields: {}".format(
172
+ ", ".join(missing)
173
+ ),
174
+ }
175
+ sys.stderr.write(json.dumps(detail, indent=2, ensure_ascii=False) + "\n")
176
+ print("crashed")
177
+ sys.exit(0)
178
+
179
+ # Determine status
180
+ resolved_status = determine_status(data)
181
+
182
+ # Build and emit detail report to stderr
183
+ detail = build_detail_report(data, resolved_status)
184
+ sys.stderr.write(json.dumps(detail, indent=2, ensure_ascii=False) + "\n")
185
+
186
+ # Emit single-line status to stdout
187
+ print(resolved_status)
188
+ sys.exit(0)
189
+
190
+
191
+ if __name__ == "__main__":
192
+ try:
193
+ main()
194
+ except KeyboardInterrupt:
195
+ detail = {
196
+ "status": "crashed",
197
+ "feature_id": None,
198
+ "completed_phases": [],
199
+ "checkpoint_reached": None,
200
+ "tasks_completed": 0,
201
+ "tasks_total": 0,
202
+ "error_count": 1,
203
+ "can_resume": False,
204
+ "resume_from_phase": None,
205
+ "internal_error": "Interrupted",
206
+ }
207
+ sys.stderr.write(json.dumps(detail, indent=2, ensure_ascii=False) + "\n")
208
+ print("crashed")
209
+ sys.exit(0)
210
+ except SystemExit:
211
+ raise
212
+ except Exception as exc:
213
+ LOGGER.exception("Unhandled exception in check-session-status")
214
+ detail = {
215
+ "status": "crashed",
216
+ "feature_id": None,
217
+ "completed_phases": [],
218
+ "checkpoint_reached": None,
219
+ "tasks_completed": 0,
220
+ "tasks_total": 0,
221
+ "error_count": 1,
222
+ "can_resume": False,
223
+ "resume_from_phase": None,
224
+ "internal_error": str(exc),
225
+ }
226
+ sys.stderr.write(json.dumps(detail, indent=2, ensure_ascii=False) + "\n")
227
+ print("crashed")
228
+ sys.exit(0)
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env python3
2
+ """Clean up pipeline session logs by age and total size.
3
+
4
+ Targets files under any `.../sessions/.../logs/` directory inside a state dir.
5
+
6
+ Policies:
7
+ 1) Remove files older than retention window.
8
+ 2) If total remaining size still exceeds max threshold, remove oldest files first
9
+ until within threshold.
10
+
11
+ Usage:
12
+ python3 cleanup-logs.py --state-dir .prizmkit/state/features
13
+ python3 cleanup-logs.py --state-dir .prizmkit/state/bugfix --retention-days 30 --max-total-mb 2048
14
+ """
15
+
16
+ import argparse
17
+ import json
18
+ import os
19
+ import time
20
+
21
+ from utils import error_out, setup_logging
22
+
23
+ LOGGER = setup_logging("cleanup-logs")
24
+
25
+
26
+ def parse_args():
27
+ parser = argparse.ArgumentParser(description="Cleanup pipeline logs by age and total size.")
28
+ parser.add_argument("--state-dir", required=True, help="State directory to scan")
29
+ parser.add_argument(
30
+ "--retention-days",
31
+ type=int,
32
+ default=14,
33
+ help="Delete logs older than this many days (default: 14)",
34
+ )
35
+ parser.add_argument(
36
+ "--max-total-mb",
37
+ type=int,
38
+ default=1024,
39
+ help="Target max total log size in MB after cleanup (default: 1024)",
40
+ )
41
+ parser.add_argument("--dry-run", action="store_true", help="Report actions without deleting")
42
+ return parser.parse_args()
43
+
44
+
45
+ def iter_log_files(state_dir):
46
+ """Yield absolute paths of files inside .../sessions/.../logs/ directories."""
47
+ for root, _dirs, files in os.walk(state_dir):
48
+ if os.path.basename(root) != "logs":
49
+ continue
50
+
51
+ normalized = root.replace("\\", "/")
52
+ if "/sessions/" not in normalized:
53
+ continue
54
+
55
+ for name in files:
56
+ if name == ".DS_Store":
57
+ continue
58
+ yield os.path.join(root, name)
59
+
60
+
61
+ def file_info(path):
62
+ """Return file metadata dict with path, size, and mtime."""
63
+ st = os.stat(path)
64
+ return {"path": path, "size": st.st_size, "mtime": st.st_mtime}
65
+
66
+
67
+ def remove_file(path, dry_run=False):
68
+ if dry_run:
69
+ return True
70
+ try:
71
+ os.remove(path)
72
+ return True
73
+ except OSError:
74
+ return False
75
+
76
+
77
+ def cleanup_empty_dirs(state_dir, dry_run=False):
78
+ """Remove empty logs directories bottom-up."""
79
+ removed = 0
80
+ for root, dirs, _files in os.walk(state_dir, topdown=False):
81
+ for d in dirs:
82
+ full = os.path.join(root, d)
83
+ if os.path.basename(full) != "logs":
84
+ continue
85
+ try:
86
+ if not os.listdir(full):
87
+ if not dry_run:
88
+ os.rmdir(full)
89
+ removed += 1
90
+ except OSError:
91
+ continue
92
+ return removed
93
+
94
+
95
+ def main():
96
+ args = parse_args()
97
+ state_dir = os.path.abspath(args.state_dir)
98
+
99
+ if not os.path.isdir(state_dir):
100
+ error_out("State directory not found: {}".format(state_dir), code=2)
101
+
102
+ if args.retention_days < 0:
103
+ error_out("retention-days must be >= 0", code=2)
104
+ if args.max_total_mb < 0:
105
+ error_out("max-total-mb must be >= 0", code=2)
106
+
107
+ now = time.time()
108
+ retention_cutoff = now - (args.retention_days * 86400)
109
+ max_total_bytes = args.max_total_mb * 1024 * 1024
110
+
111
+ files = []
112
+ for path in iter_log_files(state_dir):
113
+ try:
114
+ files.append(file_info(path))
115
+ except OSError:
116
+ continue
117
+
118
+ initial_total = sum(f["size"] for f in files)
119
+
120
+ deleted_files = []
121
+ kept_files = []
122
+
123
+ # Step 1: age-based cleanup
124
+ for f in files:
125
+ if f["mtime"] < retention_cutoff:
126
+ if remove_file(f["path"], dry_run=args.dry_run):
127
+ deleted_files.append({**f, "reason": "retention"})
128
+ else:
129
+ kept_files.append(f)
130
+ else:
131
+ kept_files.append(f)
132
+
133
+ # Step 2: size-based cleanup (oldest first)
134
+ current_total = sum(f["size"] for f in kept_files)
135
+ if current_total > max_total_bytes:
136
+ kept_files.sort(key=lambda x: x["mtime"]) # oldest first
137
+ still_kept = []
138
+ for f in kept_files:
139
+ if current_total <= max_total_bytes:
140
+ still_kept.append(f)
141
+ continue
142
+
143
+ if remove_file(f["path"], dry_run=args.dry_run):
144
+ deleted_files.append({**f, "reason": "size"})
145
+ current_total -= f["size"]
146
+ else:
147
+ still_kept.append(f)
148
+
149
+ kept_files = still_kept
150
+
151
+ removed_empty_log_dirs = cleanup_empty_dirs(state_dir, dry_run=args.dry_run)
152
+
153
+ final_total = sum(f["size"] for f in kept_files)
154
+ reclaimed = initial_total - final_total
155
+
156
+ report = {
157
+ "success": True,
158
+ "state_dir": state_dir,
159
+ "dry_run": args.dry_run,
160
+ "retention_days": args.retention_days,
161
+ "max_total_mb": args.max_total_mb,
162
+ "initial_files": len(files),
163
+ "deleted_files": len(deleted_files),
164
+ "deleted_by_reason": {
165
+ "retention": sum(1 for f in deleted_files if f["reason"] == "retention"),
166
+ "size": sum(1 for f in deleted_files if f["reason"] == "size"),
167
+ },
168
+ "removed_empty_log_dirs": removed_empty_log_dirs,
169
+ "initial_total_bytes": initial_total,
170
+ "final_total_bytes": final_total,
171
+ "reclaimed_bytes": reclaimed,
172
+ }
173
+
174
+ LOGGER.info(
175
+ "cleanup complete: deleted=%s reclaimed=%sKB",
176
+ report["deleted_files"],
177
+ int(report["reclaimed_bytes"] / 1024),
178
+ )
179
+
180
+ print(json.dumps(report, indent=2, ensure_ascii=False))
181
+
182
+
183
+ if __name__ == "__main__":
184
+ try:
185
+ main()
186
+ except KeyboardInterrupt:
187
+ error_out("cleanup-logs interrupted", code=130)
188
+ except SystemExit:
189
+ raise
190
+ except Exception as exc:
191
+ LOGGER.exception("Unhandled exception in cleanup-logs")
192
+ error_out("cleanup-logs failed: {}".format(str(exc)), code=1)