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.
- package/bin/create-prizmkit.js +8 -6
- package/bundled/VERSION.json +3 -3
- package/bundled/adapters/codex/agent-adapter.js +38 -0
- package/bundled/adapters/codex/paths.js +27 -0
- package/bundled/adapters/codex/rules-adapter.js +30 -0
- package/bundled/adapters/codex/settings-adapter.js +27 -0
- package/bundled/adapters/codex/skill-adapter.js +65 -0
- package/bundled/adapters/codex/team-adapter.js +37 -0
- package/bundled/dev-pipeline/.env.example +2 -1
- package/bundled/dev-pipeline/README.md +10 -7
- package/bundled/dev-pipeline/lib/common.sh +278 -37
- package/bundled/dev-pipeline/run-bugfix.sh +10 -61
- package/bundled/dev-pipeline/run-feature.sh +10 -78
- package/bundled/dev-pipeline/run-recovery.sh +10 -46
- package/bundled/dev-pipeline/run-refactor.sh +10 -61
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +17 -7
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +9 -3
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +9 -3
- package/bundled/dev-pipeline/scripts/utils.py +6 -4
- package/bundled/dev-pipeline-windows/.env.example +28 -0
- package/bundled/dev-pipeline-windows/README.md +30 -0
- package/bundled/dev-pipeline-windows/SCHEMA_ANALYSIS.md +525 -0
- package/bundled/dev-pipeline-windows/assets/feature-list-example.json +146 -0
- package/bundled/dev-pipeline-windows/assets/prizm-dev-team-integration.md +138 -0
- package/bundled/dev-pipeline-windows/launch-bugfix-daemon.ps1 +9 -0
- package/bundled/dev-pipeline-windows/launch-feature-daemon.ps1 +9 -0
- package/bundled/dev-pipeline-windows/launch-refactor-daemon.ps1 +9 -0
- package/bundled/dev-pipeline-windows/lib/common.ps1 +432 -0
- package/bundled/dev-pipeline-windows/lib/daemon.ps1 +140 -0
- package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +446 -0
- package/bundled/dev-pipeline-windows/lib/reset.ps1 +87 -0
- package/bundled/dev-pipeline-windows/reset-bug.ps1 +9 -0
- package/bundled/dev-pipeline-windows/reset-feature.ps1 +9 -0
- package/bundled/dev-pipeline-windows/reset-refactor.ps1 +9 -0
- package/bundled/dev-pipeline-windows/run-bugfix.ps1 +9 -0
- package/bundled/dev-pipeline-windows/run-feature.ps1 +9 -0
- package/bundled/dev-pipeline-windows/run-recovery.ps1 +76 -0
- package/bundled/dev-pipeline-windows/run-refactor.ps1 +9 -0
- package/bundled/dev-pipeline-windows/scripts/check-session-status.py +228 -0
- package/bundled/dev-pipeline-windows/scripts/cleanup-logs.py +192 -0
- package/bundled/dev-pipeline-windows/scripts/detect-stuck.py +530 -0
- package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +1737 -0
- package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +685 -0
- package/bundled/dev-pipeline-windows/scripts/generate-recovery-prompt.py +805 -0
- package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +763 -0
- package/bundled/dev-pipeline-windows/scripts/init-bugfix-pipeline.py +316 -0
- package/bundled/dev-pipeline-windows/scripts/init-dev-team.py +134 -0
- package/bundled/dev-pipeline-windows/scripts/init-pipeline.py +380 -0
- package/bundled/dev-pipeline-windows/scripts/init-refactor-pipeline.py +399 -0
- package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +388 -0
- package/bundled/dev-pipeline-windows/scripts/patch-completion-notes.py +191 -0
- package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +864 -0
- package/bundled/dev-pipeline-windows/scripts/update-checkpoint.py +173 -0
- package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +1501 -0
- package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +1073 -0
- package/bundled/dev-pipeline-windows/scripts/utils.py +542 -0
- package/bundled/dev-pipeline-windows/templates/agent-prompts/critic-plan-challenge.md +7 -0
- package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-fix.md +7 -0
- package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-implement.md +30 -0
- package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-resume.md +5 -0
- package/bundled/dev-pipeline-windows/templates/agent-prompts/reviewer-review.md +7 -0
- package/bundled/dev-pipeline-windows/templates/bootstrap-prompt.md +46 -0
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +43 -0
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +43 -0
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +43 -0
- package/bundled/dev-pipeline-windows/templates/bug-fix-list-schema.json +263 -0
- package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +320 -0
- package/bundled/dev-pipeline-windows/templates/feature-list-schema.json +237 -0
- package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +331 -0
- package/bundled/dev-pipeline-windows/templates/refactor-list-schema.json +270 -0
- package/bundled/dev-pipeline-windows/templates/sections/ac-verification-checklist.md +13 -0
- package/bundled/dev-pipeline-windows/templates/sections/checkpoint-system.md +91 -0
- package/bundled/dev-pipeline-windows/templates/sections/context-budget-rules.md +33 -0
- package/bundled/dev-pipeline-windows/templates/sections/critical-paths-agent.md +10 -0
- package/bundled/dev-pipeline-windows/templates/sections/critical-paths-full.md +12 -0
- package/bundled/dev-pipeline-windows/templates/sections/critical-paths-lite.md +7 -0
- package/bundled/dev-pipeline-windows/templates/sections/directory-convention-agent.md +8 -0
- package/bundled/dev-pipeline-windows/templates/sections/directory-convention-full.md +9 -0
- package/bundled/dev-pipeline-windows/templates/sections/directory-convention-lite.md +6 -0
- package/bundled/dev-pipeline-windows/templates/sections/failure-capture.md +21 -0
- package/bundled/dev-pipeline-windows/templates/sections/feature-context.md +31 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-auto.md +72 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-opencli.md +63 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification.md +62 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-commit-full.md +71 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-commit.md +64 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-agent-suffix.md +23 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-base.md +24 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-lite-suffix.md +12 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan-full.md +53 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan.md +32 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-implement-agent.md +37 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-implement-full.md +50 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-implement-lite.md +52 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-plan-agent.md +27 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-plan-lite.md +27 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-review-agent.md +27 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-review-full.md +29 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-specify-plan-full.md +77 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase0-init.md +13 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase0-test-baseline.md +23 -0
- package/bundled/dev-pipeline-windows/templates/sections/session-context.md +5 -0
- package/bundled/dev-pipeline-windows/templates/sections/subagent-timeout-recovery.md +6 -0
- package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-agent.md +67 -0
- package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-lite.md +58 -0
- package/bundled/dev-pipeline-windows/templates/session-status-schema.json +83 -0
- package/bundled/skills/_metadata.json +1 -1
- package/bundled/skills/app-planner/SKILL.md +26 -18
- package/bundled/skills/app-planner/references/architecture-decisions.md +9 -5
- package/bundled/skills/app-planner/references/frontend-design-guide.md +1 -1
- package/bundled/skills/feature-planner/SKILL.md +9 -2
- package/bundled/skills/prizmkit-init/SKILL.md +7 -6
- package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +2 -0
- package/bundled/skills-windows/app-planner/SKILL.md +639 -0
- package/bundled/skills-windows/app-planner/assets/app-design-guide.md +101 -0
- package/bundled/skills-windows/app-planner/references/architecture-decisions.md +52 -0
- package/bundled/skills-windows/app-planner/references/brainstorm-guide.md +101 -0
- package/bundled/skills-windows/app-planner/references/frontend-design-guide.md +71 -0
- package/bundled/skills-windows/app-planner/references/project-brief-guide.md +82 -0
- package/bundled/skills-windows/app-planner/references/red-team-checklist.md +40 -0
- package/bundled/skills-windows/app-planner/references/rules/backend/derivation-rules.md +609 -0
- package/bundled/skills-windows/app-planner/references/rules/backend/fixed-rules.md +285 -0
- package/bundled/skills-windows/app-planner/references/rules/backend/question-bank.md +249 -0
- package/bundled/skills-windows/app-planner/references/rules/backend/template.md +173 -0
- package/bundled/skills-windows/app-planner/references/rules/database/derivation-rules.md +373 -0
- package/bundled/skills-windows/app-planner/references/rules/database/fixed-rules.md +211 -0
- package/bundled/skills-windows/app-planner/references/rules/database/question-bank.md +184 -0
- package/bundled/skills-windows/app-planner/references/rules/database/template.md +158 -0
- package/bundled/skills-windows/app-planner/references/rules/frontend/derivation-rules.md +810 -0
- package/bundled/skills-windows/app-planner/references/rules/frontend/fixed-rules.md +188 -0
- package/bundled/skills-windows/app-planner/references/rules/frontend/question-bank.md +302 -0
- package/bundled/skills-windows/app-planner/references/rules/frontend/template.md +320 -0
- package/bundled/skills-windows/app-planner/references/rules/mobile/derivation-rules.md +639 -0
- package/bundled/skills-windows/app-planner/references/rules/mobile/fixed-rules.md +290 -0
- package/bundled/skills-windows/app-planner/references/rules/mobile/question-bank.md +232 -0
- package/bundled/skills-windows/app-planner/references/rules/mobile/template.md +175 -0
- package/bundled/skills-windows/bug-fix-workflow/SKILL.md +415 -0
- package/bundled/skills-windows/bug-planner/SKILL.md +395 -0
- package/bundled/skills-windows/bug-planner/assets/bug-confirmation-template.md +43 -0
- package/bundled/skills-windows/bug-planner/references/critic-and-verification.md +44 -0
- package/bundled/skills-windows/bug-planner/references/error-recovery.md +73 -0
- package/bundled/skills-windows/bug-planner/references/input-formats.md +53 -0
- package/bundled/skills-windows/bug-planner/references/schema-validation.md +25 -0
- package/bundled/skills-windows/bug-planner/references/severity-rules.md +16 -0
- package/bundled/skills-windows/bug-planner/scripts/validate-bug-list.py +322 -0
- package/bundled/skills-windows/bugfix-pipeline-launcher/SKILL.md +380 -0
- package/bundled/skills-windows/feature-pipeline-launcher/SKILL.md +441 -0
- package/bundled/skills-windows/feature-pipeline-launcher/scripts/preflight-check.py +462 -0
- package/bundled/skills-windows/feature-planner/SKILL.md +401 -0
- package/bundled/skills-windows/feature-planner/assets/evaluation-guide.md +64 -0
- package/bundled/skills-windows/feature-planner/assets/planning-guide.md +214 -0
- package/bundled/skills-windows/feature-planner/references/browser-interaction.md +59 -0
- package/bundled/skills-windows/feature-planner/references/completeness-review.md +57 -0
- package/bundled/skills-windows/feature-planner/references/decomposition-patterns.md +75 -0
- package/bundled/skills-windows/feature-planner/references/error-recovery.md +90 -0
- package/bundled/skills-windows/feature-planner/references/incremental-feature-planning.md +112 -0
- package/bundled/skills-windows/feature-planner/references/new-project-planning.md +85 -0
- package/bundled/skills-windows/feature-planner/scripts/validate-and-generate.py +1029 -0
- package/bundled/skills-windows/feature-workflow/SKILL.md +531 -0
- package/bundled/skills-windows/prizmkit-init/SKILL.md +356 -0
- package/bundled/skills-windows/prizmkit-init/assets/project-brief-template.md +82 -0
- package/bundled/skills-windows/prizmkit-init/references/config-schema.md +68 -0
- package/bundled/skills-windows/prizmkit-init/references/rules/layer-detection.md +41 -0
- package/bundled/skills-windows/prizmkit-init/references/tech-stack-catalog.md +13 -0
- package/bundled/skills-windows/prizmkit-init/references/update-supplement.md +9 -0
- package/bundled/skills-windows/recovery-workflow/SKILL.md +456 -0
- package/bundled/skills-windows/recovery-workflow/evals/evals.json +46 -0
- package/bundled/skills-windows/recovery-workflow/scripts/detect-recovery-state.py +544 -0
- package/bundled/skills-windows/refactor-pipeline-launcher/SKILL.md +406 -0
- package/bundled/skills-windows/refactor-planner/SKILL.md +540 -0
- package/bundled/skills-windows/refactor-planner/assets/planning-guide.md +292 -0
- package/bundled/skills-windows/refactor-planner/references/behavior-preservation.md +301 -0
- package/bundled/skills-windows/refactor-planner/references/refactor-scoping-guide.md +221 -0
- package/bundled/skills-windows/refactor-planner/scripts/validate-and-generate-refactor.py +858 -0
- package/bundled/skills-windows/refactor-workflow/SKILL.md +503 -0
- package/package.json +3 -2
- package/src/clean.js +73 -2
- package/src/config.js +159 -50
- package/src/detect-platform.js +16 -8
- package/src/external-skills.js +26 -19
- package/src/index.js +31 -9
- package/src/manifest.js +6 -2
- package/src/metadata.js +43 -5
- package/src/platforms.js +36 -0
- package/src/prompts.js +31 -6
- package/src/runtimes.js +20 -0
- package/src/scaffold.js +314 -110
- package/src/upgrade.js +81 -41
|
@@ -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,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)
|