prizmkit 1.1.91 → 1.1.93

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 (46) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/.env.example +0 -4
  3. package/bundled/dev-pipeline/README.md +9 -39
  4. package/bundled/dev-pipeline/SCHEMA_ANALYSIS.md +0 -4
  5. package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +0 -2
  6. package/bundled/dev-pipeline/launch-bugfix-daemon.sh +0 -1
  7. package/bundled/dev-pipeline/launch-feature-daemon.sh +2 -3
  8. package/bundled/dev-pipeline/launch-refactor-daemon.sh +0 -1
  9. package/bundled/dev-pipeline/lib/branch.sh +8 -5
  10. package/bundled/dev-pipeline/lib/common.sh +122 -0
  11. package/bundled/dev-pipeline/lib/heartbeat.sh +13 -3
  12. package/bundled/dev-pipeline/run-bugfix.sh +149 -98
  13. package/bundled/dev-pipeline/run-feature.sh +90 -103
  14. package/bundled/dev-pipeline/run-recovery.sh +1 -32
  15. package/bundled/dev-pipeline/run-refactor.sh +148 -99
  16. package/bundled/dev-pipeline/scripts/update-bug-status.py +39 -4
  17. package/bundled/dev-pipeline/scripts/update-feature-status.py +43 -6
  18. package/bundled/dev-pipeline/scripts/update-refactor-status.py +36 -4
  19. package/bundled/dev-pipeline/tests/test-deploy-safety.sh +3 -3
  20. package/bundled/dev-pipeline/tests/test_auto_skip.py +92 -3
  21. package/bundled/dev-pipeline-windows/.env.example +0 -4
  22. package/bundled/dev-pipeline-windows/SCHEMA_ANALYSIS.md +0 -4
  23. package/bundled/dev-pipeline-windows/assets/prizm-dev-team-integration.md +0 -2
  24. package/bundled/dev-pipeline-windows/lib/branch.ps1 +17 -4
  25. package/bundled/dev-pipeline-windows/lib/common.ps1 +9 -0
  26. package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +89 -108
  27. package/bundled/dev-pipeline-windows/run-recovery.ps1 +4 -19
  28. package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +39 -4
  29. package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +43 -6
  30. package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +36 -4
  31. package/bundled/skills/_metadata.json +1 -1
  32. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +1 -1
  33. package/bundled/skills/bugfix-pipeline-launcher/references/configuration.md +5 -15
  34. package/bundled/skills/feature-pipeline-launcher/SKILL.md +4 -4
  35. package/bundled/skills/feature-pipeline-launcher/references/configuration.md +3 -13
  36. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +3 -3
  37. package/bundled/skills/refactor-pipeline-launcher/references/configuration.md +4 -14
  38. package/bundled/skills-windows/bugfix-pipeline-launcher/SKILL.md +1 -1
  39. package/bundled/skills-windows/bugfix-pipeline-launcher/references/configuration.md +4 -14
  40. package/bundled/skills-windows/feature-pipeline-launcher/SKILL.md +4 -4
  41. package/bundled/skills-windows/feature-pipeline-launcher/references/configuration.md +2 -12
  42. package/bundled/skills-windows/refactor-pipeline-launcher/SKILL.md +3 -3
  43. package/bundled/skills-windows/refactor-pipeline-launcher/references/configuration.md +4 -14
  44. package/package.json +1 -1
  45. package/bundled/dev-pipeline/scripts/cleanup-logs.py +0 -192
  46. package/bundled/dev-pipeline-windows/scripts/cleanup-logs.py +0 -192
@@ -14,7 +14,6 @@ Translating user responses to env vars:
14
14
  | Strict behavior: On | `STRICT_BEHAVIOR_CHECK=1` |
15
15
  | Strict behavior: Off | `STRICT_BEHAVIOR_CHECK=0` |
16
16
  | Critic: On | `ENABLE_CRITIC=true` |
17
- | Timeout: value | `SESSION_TIMEOUT=<seconds>` |
18
17
  | Stop on failure: On | `STOP_ON_FAILURE=1` |
19
18
  | Deploy: Yes | `ENABLE_DEPLOY=1` |
20
19
  | Effort: value | `PRIZMKIT_EFFORT=<value>` |
@@ -30,9 +29,6 @@ Not exposed in interactive menu, pass via `--env`:
30
29
  | `DEV_BRANCH` | auto-generated | Custom dev branch name (default: `refactor/pipeline-{run_id}`) |
31
30
  | `HEARTBEAT_INTERVAL` | `30` | Heartbeat log interval in seconds |
32
31
  | `HEARTBEAT_STALE_THRESHOLD` | `600` | Max seconds without heartbeat before marking stale |
33
- | `LOG_CLEANUP_ENABLED` | `1` | Run periodic log cleanup (`0` to disable) |
34
- | `LOG_RETENTION_DAYS` | `14` | Delete logs older than N days |
35
- | `LOG_MAX_TOTAL_MB` | `1024` | Keep total logs under N MB via oldest-first cleanup |
36
32
 
37
33
  ## Error Handling
38
34
 
@@ -57,23 +53,17 @@ Not exposed in interactive menu, pass via `--env`:
57
53
 
58
54
  Only run this when the user answered "Yes" to the **Advanced config?** question in step 6. It applies to a minority of sessions.
59
55
 
60
- Ask a second round of `AskUserQuestion` with these 4 questions:
56
+ Ask a second round of `AskUserQuestion` with these 3 questions:
61
57
 
62
- **Question 1 — Session timeout** (multiSelect: false):
63
- - None (default) — No timeout
64
- - 30 min — `SESSION_TIMEOUT=1800`
65
- - 1 hour — `SESSION_TIMEOUT=3600`
66
- - 2 hours — `SESSION_TIMEOUT=7200`
67
-
68
- **Question 2 — Stop on failure** (multiSelect: false):
58
+ **Question 1 — Stop on failure** (multiSelect: false):
69
59
  - Off (default) — Pipeline continues to next task after failure
70
60
  - On — Pipeline halts immediately when a task exhausts all retries (`STOP_ON_FAILURE=1`)
71
61
 
72
- **Question 3 — Critic review** (multiSelect: false):
62
+ **Question 2 — Critic review** (multiSelect: false):
73
63
  - Off (default) — Skip adversarial review
74
64
  - On — Enable adversarial critic review: an independent AI agent reviews the refactor plan for completeness and the implementation for regressions, missing edge cases, and behavior violations. Adds ~5-10 min per refactor task.
75
65
 
76
- **Question 4 — Reasoning effort** (multiSelect: false):
66
+ **Question 3 — Reasoning effort** (multiSelect: false):
77
67
  - Default (none) — Use CLI default
78
68
  - low — Minimize reasoning, fastest output (`PRIZMKIT_EFFORT=low`)
79
69
  - medium — Moderate reasoning (`PRIZMKIT_EFFORT=medium`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prizmkit",
3
- "version": "1.1.91",
3
+ "version": "1.1.93",
4
4
  "description": "Create a new PrizmKit-powered project with clean initialization — no framework dev files, just what you need.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,192 +0,0 @@
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)
@@ -1,192 +0,0 @@
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)